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

# Contact & Label Tools

> Reference for the contacts:manage MCP tools — find, create, update, and label contacts, and resolve the numeric label ids campaigns and filters need.

Seven tools for working with your account's contacts and its label catalog. All of them require the **`contacts:manage`** scope — a key without it gets an auth error naming the missing scope.

<Info>
  `list_labels` is the **source of truth for label ids**. Campaign audiences (`preview_campaign_audience`, `draft_campaign`) and label-based filters take **numeric label ids**, not title strings — resolve them here first.
</Info>

## Two ids, don't mix them

<CardGroup cols={2}>
  <Card title="Contact database id" icon="table">
    The <code>id</code> on a contact from <code>list\_contacts</code> / <code>search\_contacts</code>. This is what <code>update\_contact</code> and <code>add\_contact\_labels</code> take.
  </Card>

  <Card title="Conversation display id" icon="diagram-project">
    The per-account number shown in the UI, used by the conversation tools. It is <b>not</b> a contact id — never pass one where a <code>contact\_id</code> is expected.
  </Card>
</CardGroup>

<Warning>
  `add_contact_labels` **replaces** the contact's entire label set — it is not additive. To add one label, read the contact's current labels first and send the union.
</Warning>

## Finding contacts

### list\_contacts

List contacts in the account, **15 per page**.

| Argument | Type          | Default | Description                                                                                                                                                                      |
| -------- | ------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `page`   | int           | `1`     | 1-based page number.                                                                                                                                                             |
| `sort`   | string        | —       | Sortable field: `name`, `email`, `phone_number`, `last_activity_at`, `created_at`, `company_name`, `city`, `country`. Prefix with `-` for descending (e.g. `-last_activity_at`). |
| `labels` | list\[string] | —       | Contact label **titles**; matches contacts having ANY of them.                                                                                                                   |

Returns `{meta:{count, current_page}, payload:[contact,...]}`.

### search\_contacts

Search contacts by name, email, phone number, or identifier.

| Argument | Type   | Default | Description             |
| -------- | ------ | ------- | ----------------------- |
| `query`  | string | —       | The text to search for. |
| `page`   | int    | `1`     | 1-based page number.    |

Returns `{meta:{count, current_page, has_more}, payload:[contact,...]}`.

### filter\_contacts

Advanced filtering with structured predicates.

| Argument  | Type        | Default | Description                                |
| --------- | ----------- | ------- | ------------------------------------------ |
| `payload` | list\[dict] | —       | A list of predicate objects (shape below). |
| `page`    | int         | `1`     | 1-based page number.                       |

Each predicate is shaped:

```json theme={null}
{
  "attribute_key": "email",
  "filter_operator": "contains",
  "values": ["@acme"],
  "query_operator": "and"
}
```

Returns `{meta:{count, current_page}, payload:[contact,...]}`.

<Tip>
  `query_operator` (`and` / `or`) chains predicates together — it belongs on each predicate, not on the request as a whole.
</Tip>

## Creating & updating

### create\_contact

Create a new contact. Provide **at least one identifying field** — `name`, `email`, `phone_number`, or `identifier`.

| Argument            | Type   | Description                           |
| ------------------- | ------ | ------------------------------------- |
| `name`              | string | Display name.                         |
| `email`             | string | Email address.                        |
| `phone_number`      | string | **E.164 form** (e.g. `+14155550100`). |
| `identifier`        | string | Your own external id for the contact. |
| `custom_attributes` | dict   | Arbitrary key/value metadata.         |

Returns `{payload:{contact:{...}, contact_inbox:{...}}}`.

### update\_contact

Update an existing contact. Only the fields you pass are changed.

| Argument            | Type   | Description                                                              |
| ------------------- | ------ | ------------------------------------------------------------------------ |
| `contact_id`        | int    | The contact's **database id** (from a list/search result's `id`).        |
| `name`              | string | Display name.                                                            |
| `email`             | string | Email address.                                                           |
| `phone_number`      | string | E.164 form.                                                              |
| `identifier`        | string | External id.                                                             |
| `custom_attributes` | dict   | **Merged** into existing attributes — keys you don't send are preserved. |

Returns `{payload:{contact...}}`.

<Note>
  `custom_attributes` are **merged** on update, so you can set one key without wiping the rest. This differs from labels, which are replaced wholesale.
</Note>

## Labels

### list\_labels

List the account's labels with their numeric ids and titles. This is what you use to resolve the ids that `preview_campaign_audience` and `draft_campaign` require.

Takes no arguments. Returns `{payload:[{id, title, description, color, ...}]}`.

### add\_contact\_labels

Set the labels on a contact. **Replaces** the contact's current set (not additive).

| Argument     | Type          | Description                                                    |
| ------------ | ------------- | -------------------------------------------------------------- |
| `contact_id` | int           | The contact's database id.                                     |
| `labels`     | list\[string] | The complete list of label **titles** the contact should have. |

Returns `{payload:[label,...]}`.

## Recipe: tag a contact without losing existing labels

<Steps>
  <Step title="Read the current labels">
    Fetch the contact via `search_contacts` or `list_contacts` and note the labels it already has.
  </Step>

  <Step title="Send the union">
    Call `add_contact_labels(contact_id, labels=[...existing, "new-label"])` with the full merged list — anything you omit is dropped.
  </Step>
</Steps>

## Related

<CardGroup cols={2}>
  <Card title="Campaign Tools" icon="paper-plane" href="/ai-agents/campaigns">
    Label ids from <code>list\_labels</code> feed campaign audiences.
  </Card>

  <Card title="Authentication & Scopes" icon="key" href="/ai-agents/authentication">
    How the <code>contacts:manage</code> scope is granted and enforced.
  </Card>
</CardGroup>
