> ## Documentation Index
> Fetch the complete documentation index at: https://ago.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# CRM API

> Read and write companies, contacts and deals, import records, merge duplicates, push signals and follow customer health through the Public API v1 and MCP

The CRM API gives your scripts and AI assistants the records of **Admin** > **Customers**: companies, contacts, deals, notes and custom fields, plus import and merge. See [Customers](https://ago.mintlify.site/features/customers.md) for the section itself.

## Before you start

* **Customers must be enabled for your workspace.** It is off by default; contact AGO to enable it. Until then, every call returns `403` with code `crm_disabled` and the MCP CRM tools are hidden.
* **Calls run as an active staff user.** For an API key, that is the user who created the key; for OAuth, the user who granted access. Otherwise calls return `403` with code `staff_principal_required`. Changes appear in each record's history under that user's name.
* **Scopes**: `crm:read` to read; `crm:write` to create, update, import, merge and erase. `crm:write` includes `crm:read`.

Create the key in **Developer** > **API Keys**. See [API Key Authentication](https://ago.mintlify.site/security/api-key-authentication.md).

## Endpoints

All paths start with `/api/v1/crm`. The lists of companies, contacts, deals and alerts and the company history accept `page` and `page_size` (at most 100) and return `{"object": "list", "data": [...], "has_more": ..., "total": ...}`. Other lists return `{"data": [...]}` without paging, and timelines page with `before` and `limit`.

| Method | Path | Does |
| - | - | - |
| GET | `/companies` | List companies (`search`, `lifecycle_stage`, `owner_id`, `sort`) |
| POST | `/companies` | Create a company |
| GET, PUT, DELETE | `/companies/{company_id}` | Read, update or delete a company |
| GET | `/companies/{company_id}/timeline` | Conversations, tickets, notes, deals, tasks and changes, newest first (`before`, `limit`) |
| GET | `/companies/{company_id}/history` | Field changes |
| POST | `/companies/merge` | Merge duplicates into `primary_id` |
| GET | `/contacts` | List contacts (`search`, `organization_id`, `owner_id`, `has_user`, `email_opt_out`) |
| POST | `/contacts` | Create a contact |
| GET, PUT | `/contacts/{contact_id}` | Read or update a contact |
| DELETE | `/contacts/{contact_id}` | Erase a contact, its notes, facts, history and customer requests |
| GET | `/contacts/{contact_id}/export` | Everything held about the contact |
| GET | `/contacts/{contact_id}/timeline` | Activity of the contact |
| POST | `/contacts/merge` | Merge duplicates into `primary_id` |
| GET | `/pipelines` | Pipelines and their stages |
| GET | `/deals` | List deals (`pipeline_id`, `stage_id`, `organization_id`, `contact_id`, `owner_id`, `status`, `search`) |
| POST | `/deals` | Create a deal |
| GET, PUT, DELETE | `/deals/{deal_id}` | Read, update or delete a deal |
| POST | `/deals/{deal_id}/move` | Move a deal to `stage_id`, optionally between `before_deal_id` and `after_deal_id` |
| POST | `/notes` | Add a note to one `company_id`, `contact_id` or `deal_id` |
| GET, POST | `/fields` | List or create custom fields |
| POST | `/import/suggest-mapping` | Suggest a target for each column header |
| POST | `/import` | Import up to 1000 rows |
| GET | `/duplicates` | Likely duplicates (`entity`: `company` or `contact`) |

### Customer success

| Method | Path | Does |
| - | - | - |
| GET | `/portfolio` | A staff member's companies, renewals, alerts and tasks (`user_id`: `me` or an id; `role`: `any`, `csm` or `owner`) |
| GET | `/companies/{company_id}/health/history` | Daily health scores (`days`, default 90) |
| POST | `/companies/{company_id}/health/recompute` | Recompute the health score now and check alerts |
| GET | `/companies/{company_id}/signals` | List a company's signals |
| POST | `/signals` | Push signals |
| GET | `/signals/keys` | Signal keys received so far |
| DELETE | `/signals/{signal_id}` | Delete a signal |
| GET, PUT | `/settings/health` | Weights, thresholds and signal components of the health score |
| GET, PUT | `/settings/alerts` | Alert rules and renewal settings |
| GET | `/companies/{company_id}/brief` | The AI brief, with its sources |
| POST | `/companies/{company_id}/brief/generate` | Write the brief again now (10 to 30 seconds) |
| GET, POST | `/companies/{company_id}/facts` | List facts (`status`, `kind`); add a confirmed fact |
| PUT, DELETE | `/facts/{fact_id}` | Confirm, reject or edit a fact; delete it |
| GET | `/alerts` | Alerts (`status`, `company_id`, `rule`, `assignee`: `me` or an id) |
| PUT | `/alerts/{alert_id}` | Resolve, dismiss or reopen an alert |
| POST | `/companies/{company_id}/account-review` | Draft an account review in Markdown (`period_days`, default 90) |

Companies also accept `csm_id`, `arr`, `currency`, `plan`, `contract_start_date` and `renewal_date`, return a `health` object (score, status, previous score and the reason of each component), and can be filtered by `csm_id`, `health_status` (`healthy`, `watch`, `at_risk` or `none`) and `renewal_before`, and sorted by `health`, `renewal_date` or `-arr`. Deals return `kind` (`new_business` or `renewal`) and can be filtered by it.

Updates write only the fields you send. `custom_fields` is merged into the record: send `null` for a key to clear it. Sending `null` for `owner_id` or `organization_id` clears it.

## Import rows

Send the rows keyed by column name, and a `mapping` from each column to a target. Get the targets, including your custom fields as `custom:<key>`, from `POST /import/suggest-mapping`.

```bash theme={null}
curl -X POST "https://your-tenant-id.api.useago.com/api/v1/crm/import" \
  -H "X-API-Key: ago_v1_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "entity": "contact",
    "rows": [{"Email": "jane@example.com", "First name": "Jane", "Company": "Example Inc", "Plan": "Enterprise"}],
    "mapping": {"Email": "email", "First name": "first_name", "Company": "company_name", "Plan": "custom:plan"},
    "dry_run": true,
    "update_existing": false
  }'
```

The response gives a `summary` (`created`, `updated`, `skipped`, `errors`) and one result per row, with the reason when a row is rejected. With `dry_run`, nothing is saved.

## Signals

Push what AGO cannot see, such as product usage or NPS answers. Name each company by `company_id` or by one of its email domains. Rows with an error are returned with their index and skipped; the others are saved.

```bash theme={null}
curl -X POST "https://your-tenant-id.api.useago.com/api/v1/crm/signals" \
  -H "X-API-Key: ago_v1_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "signals": [
      {"company_domain": "example.com", "key": "product_usage", "value": 72},
      {"company_domain": "example.com", "key": "nps", "value": 5, "text": "Too slow on mobile"}
    ]
  }'
```

A signal counts in the health score once its key is added to `signal_components` in `/settings/health`. The `nps` key, from 0 to 10, also drives the detractor alert. See [Customers](https://ago.mintlify.site/features/customers.md#signals).

## Errors

Errors follow the [Public API format](https://ago.mintlify.site/api/errors.md). The `code` says what to fix, and `param` names the field when one is at fault:

| Code | Meaning |
| - | - |
| `company_exists`, `contact_exists` | The name or email is already used; the message gives the existing record's id |
| `email_domain_taken` | The domain belongs to another company |
| `unknown_custom_field` | The key is not a custom field; the message lists valid keys |
| `invalid_custom_field` | The value does not fit the field type or options |
| `owner_not_assignable` | The owner is not an active staff member |
| `stage_not_found` | The stage is not in the deal's pipeline |
| `stale_neighbors` | The deals around the target position moved; reload and retry |
| `invalid_setting` | A health or alert setting is out of range; the message gives the valid values |
| `nothing_to_summarize` | The company has no conversation, ticket or note of the last 90 days to write a brief from |
| `ai_unavailable` | The model did not answer (`502`); the message gives the reason. Retry later |
| `fact_exists` | The company already has the same fact |
| `alert_already_open` | The company already has an open alert of this kind |

## MCP tools

Connected AI assistants get these tools: `list_crm_companies`, `get_crm_company`, `create_crm_company`, `update_crm_company`, `list_crm_contacts`, `get_crm_contact`, `create_crm_contact`, `update_crm_contact`, `get_crm_timeline`, `list_crm_pipelines`, `list_crm_deals`, `get_crm_deal`, `create_crm_deal`, `update_crm_deal`, `list_crm_fields`, `create_crm_field`, `add_crm_note`, `suggest_crm_import_mapping`, `import_crm_records`, `find_crm_duplicates`, `merge_crm_companies`, `merge_crm_contacts`, `get_crm_company_health`, `list_crm_portfolio`, `push_crm_signals`, `get_crm_company_brief`, `generate_crm_company_brief`, `list_crm_facts`, `update_crm_fact`, `list_crm_alerts`, `update_crm_alert` and `prepare_crm_account_review`.

Deleting records, erasing or exporting a contact, moving a deal on the board, recomputing health, health history, listing a company's signals, signal keys, adding a fact, company history and the health and alert settings are available through the API only.

To link a follow-up task to a company, contact or deal, use the [Tasks API](https://ago.mintlify.site/api/tasks-api.md) with `target_type` `organization`, `contact` or `deal`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.