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

# Manage contacts with the API

> Create, find, update and tag CloudTalk contacts from the v1 Contacts API, and map its camelCase fields to the snake_case contact webhook payloads.

The Contacts API at `https://api.cloudtalk.io/v1` reads and changes the same contacts you see under **Contacts** in the Dashboard. Use it to keep CloudTalk in step with your CRM, add leads from a web form, or find the contact behind a phone number before a call.

It covers three resources: contacts (`/contacts`), the contact tag catalogue (`/contact-tags`) and the custom field catalogue (`/contact-fields`). The older `/contacts/*.json` and `/tags/*.json` endpoints on `https://my.cloudtalk.io/api` keep working unchanged. The reference lists them under [Contacts (LEGACY)](https://help.cloudtalk.io/api-reference/contacts-legacy/list-contacts.md) and [Contact tags (LEGACY)](https://help.cloudtalk.io/api-reference/contact-tags-legacy/list-contact-tags.md), marked **LEGACY**.

> [!NOTE]
>
> Requests authenticate with an API key, like the rest of the [CloudTalk API](https://help.cloudtalk.io/guides/authentication.md). The key acts as a company admin: it sees and changes every contact in your account, and no header names a user.

## Quick start

**1. Read the custom fields and tags you can set**

```bash theme={"system"}
curl -u ACCESS_KEY_ID:ACCESS_KEY_SECRET \
  https://api.cloudtalk.io/v1/contact-fields

curl -u ACCESS_KEY_ID:ACCESS_KEY_SECRET \
  https://api.cloudtalk.io/v1/contact-tags
```

A field's `name` is the key you use in `customFields`. A tag's `id` is what `tagIds` takes. A create never makes a tag by accident: to use a new tag, create it first with [Create a contact tag](https://help.cloudtalk.io/api-reference/contacts/create-a-contact-tag.md).

**2. Create a contact, or update the one that matches**

```bash theme={"system"}
curl -u ACCESS_KEY_ID:ACCESS_KEY_SECRET \
  -X POST https://api.cloudtalk.io/v1/contacts \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: web-form-8841" \
  -d '{
    "name": "Sam Rivera",
    "type": "lead",
    "phones": ["+447700900123"],
    "emails": ["sam.rivera@example.com"],
    "tagIds": ["131"],
    "matchBy": "emailOrPhone"
  }'
```

The answer is `201` with the new contact. With `matchBy` set, a contact that matches is updated instead and the answer is `200`. When several contacts match, the most recently created one is updated and the others stay as they are.

**3. Find a contact by phone or email**

```bash theme={"system"}
curl -u ACCESS_KEY_ID:ACCESS_KEY_SECRET \
  -X POST https://api.cloudtalk.io/v1/contacts/lookup \
  -H "Content-Type: application/json" \
  -d '{"phones": ["+447700900123"]}'
```

Lookup matches exactly. To search by name or text, use [List and search contacts](https://help.cloudtalk.io/api-reference/contacts/list-and-search-contacts.md) with `search`.

**4. Change only what you send**

```bash theme={"system"}
curl -u ACCESS_KEY_ID:ACCESS_KEY_SECRET \
  -X PATCH https://api.cloudtalk.io/v1/contacts/44190 \
  -H "Content-Type: application/json" \
  -d '{"title": "Head of Purchasing", "customFields": {"Segment": "mid-market"}}'
```

Fields you leave out stay as they are. `null` clears a text field other than `name`, or removes one custom field key.

## Operations

| Operation | Request |
| - | - |
| [List and search contacts](https://help.cloudtalk.io/api-reference/contacts/list-and-search-contacts.md) | `GET /contacts` |
| [Create a contact](https://help.cloudtalk.io/api-reference/contacts/create-a-contact.md) | `POST /contacts` |
| [Look up contacts](https://help.cloudtalk.io/api-reference/contacts/look-up-contacts.md) | `POST /contacts/lookup` |
| [Bulk upsert contacts](https://help.cloudtalk.io/api-reference/contacts/bulk-upsert-contacts.md) | `POST /contacts/bulk` |
| [Get a contact](https://help.cloudtalk.io/api-reference/contacts/get-a-contact.md) | `GET /contacts/{contactId}` |
| [Update a contact](https://help.cloudtalk.io/api-reference/contacts/update-a-contact.md) | `PATCH /contacts/{contactId}` |
| [Delete a contact](https://help.cloudtalk.io/api-reference/contacts/delete-a-contact.md) | `DELETE /contacts/{contactId}` |
| [Add tags to a contact](https://help.cloudtalk.io/api-reference/contacts/add-tags-to-a-contact.md) | `POST /contacts/{contactId}/tags` |
| [Remove tags from a contact](https://help.cloudtalk.io/api-reference/contacts/remove-tags-from-a-contact.md) | `DELETE /contacts/{contactId}/tags` |
| [List contact tags](https://help.cloudtalk.io/api-reference/contacts/list-contact-tags.md) | `GET /contact-tags` |
| [Create a contact tag](https://help.cloudtalk.io/api-reference/contacts/create-a-contact-tag.md) | `POST /contact-tags` |
| [Rename a contact tag](https://help.cloudtalk.io/api-reference/contacts/rename-a-contact-tag.md) | `PATCH /contact-tags/{tagId}` |
| [Delete a contact tag](https://help.cloudtalk.io/api-reference/contacts/delete-a-contact-tag.md) | `DELETE /contact-tags/{tagId}` |
| [List contact fields](https://help.cloudtalk.io/api-reference/contacts/list-contact-fields.md) | `GET /contact-fields` |

Lists return `{ "data": [...], "pagination": { "limit": 20, "next": "..." }, "requestId": "..." }`. Set `?limit=` (default 20, at most 100) and pass `pagination.next` as `?cursor=` with the same filters to get the next page. `next` is `null` on the last page. One contact, tag or field comes back as a bare object.

## Keeping another system in step

* **Changes as they happen.** Subscribe to the [`contact.created`, `contact.updated` and `contact.deleted`](https://help.cloudtalk.io/guides/webhooks/events/contacts) webhooks. Changes made through this API send them too.
* **Catching up.** Call [List and search contacts](https://help.cloudtalk.io/api-reference/contacts/list-and-search-contacts.md) with `updatedSince` set to the time your previous run started. The list then comes most recently updated first. Some changes do not move `updatedAt`, for example Dashboard bulk edits and CRM list sync. The reference lists them. To pick those up too, list all contacts without `updatedSince` from time to time, and again after you miss webhook events or run a bulk sync.
* **Read-only tags.** Integration tags, and tags that contacts get through a CRM integration, show `readOnly: true`. You can read them but not add, remove, rename or delete them here (`422 CONTACTS.TAG_READ_ONLY`).
* **Text is cleaned up.** On create, bulk upsert, update, and when you create or rename a tag, emojis and other characters that take 4 bytes in UTF-8, and control characters such as line breaks and tabs, become a space. Repeated whitespace becomes one space, and spaces at the start and end are removed. This happens before lengths are checked, the same as in the CloudTalk Dashboard and the existing API. It applies to `name`, `title`, `company`, `industry`, `website`, `address`, `city`, `state`, `zip`, custom field values and tag names, not to emails or phone numbers. Compare cleaned values, not the raw ones you sent, when you check what was stored.

## API fields and webhook fields

The API uses camelCase. The `contact.*` webhook payloads use snake\_case and keep their own names, so the same contact reads differently in each. Use this table to map one onto the other.

| API (`/v1/contacts`) | Webhook (`contact.created`, `contact.updated`) | Notes |
| - | - | - |
| `id` | `id` | The same contact id, as a string. |
| `type` | `type` | `contact` or `lead`. |
| `name`, `title`, `company`, `industry`, `website`, `address`, `city`, `state`, `zip` | the same names | The API sends `null` for an empty field. The webhook leaves an empty field out. |
| `country.code` | `country.country_code` | ISO 3166-1 alpha-2. |
| `country.name` | `country.name` | |
| `phones` | `phones` | E.164. |
| `emails` | `emails` | |
| `tags[].name` | `tags` | The webhook sends tag names only. The API sends each tag as `{ id, name, readOnly }`, contact tags only. Match on the name. |
| `customFields` | `custom_fields` | The same map: field name to string value. A write sets at most 100 keys. |
| `favoriteUserId` | `favorite_user.id` | The webhook sends the whole user object, and leaves it out when that user was deleted. |
| `source` | `source` | |
| `createdAt` | `created_at` | The API gives whole seconds (`2026-09-14T08:30:00Z`), the webhook milliseconds. |
| `updatedAt` | `modified_at` | Same as above. |
| `dashboardUrl` | not sent | Link to the contact in the Dashboard. |
| not returned | `external_urls` | Named external links on the contact. |

`contact.deleted` carries only `id`.

## Safe retries

A network error on `POST /contacts` leaves you not knowing whether the contact was created. Send an `Idempotency-Key` header (up to 255 characters) and retry with the same key and the same body:

* The contact was created: you get the original status with the contact as it is now.
* The first request is still running: `409 IDEMPOTENCY.REQUEST_IN_PROGRESS`. Retry it shortly.
* The same key with a different body: `422 IDEMPOTENCY.KEY_REUSED`. A key names one create.
* The contact was deleted since: the same key creates a new one.

Keys are kept for 24 hours. [Bulk upsert contacts](https://help.cloudtalk.io/api-reference/contacts/bulk-upsert-contacts.md) takes no key. It always matches, so retrying the same call with the same `matchBy` updates the rows that were already written.

## Errors

Errors share one shape:

```json theme={"system"}
{
  "error": {
    "code": "VALIDATION.INVALID_FIELD",
    "message": "phones[0] is not a valid phone number in E.164 format, for example +12025550143.",
    "target": "phones[0]"
  },
  "requestId": "c9856b94-db55-402b-a6ba-adb130351158"
}
```

Requests are [rate limited](https://help.cloudtalk.io/guides/rate-limiting.md) per company. Over the limit you get `429` with `X-CloudTalkAPI-Limit`, `X-CloudTalkAPI-Remaining` and `X-CloudTalkAPI-ResetTime`, and no error body.

Branch on `code`. `message` is for people and can change, and it never repeats a value you sent. Quote `requestId` when you contact support. Each operation's reference page lists the codes it can return, and [Error codes](https://help.cloudtalk.io/guides/error-codes.md) covers the shape.


## Related topics

- [Manage webhooks with the API](https://help.cloudtalk.io/guides/webhooks/manage-with-api.md)
- [API reference](https://help.cloudtalk.io/api-reference/overview.md)
- [How to Import Contacts into CloudTalk](https://help.cloudtalk.io/en/articles/2145547-how-to-import-contacts-into-cloudtalk.md)


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