# Contacts API

URL: https://developers.cuedesk.com/api/contacts_api

> Manage contacts, custom field values, contact lists, notes and bulk imports with the Contacts API.

<Callout type="info" title="Beta">
The Contacts API is currently in beta. If you would like to join the beta, please [contact us](mailto:support@cuedesk.com?subject=Contacts%20API%20Beta%20Enrollment&body=Hi%2C%0A%0AI%27d%20like%20to%20request%20access%20to%20the%20Contacts%20API%20beta.%20Please%20enable%20this%20feature%20for%20my%20account.%0A%0AThank%20you).
</Callout>

The Contacts API allows you to manage contacts in your workspace, including their custom field values, contact list membership, notes, and bulk import operations.

See the [authentication](/api/authentication) page for how to obtain a bearer token. All endpoints require a Bearer token in the `Authorization` header:

```text
Authorization: Bearer <token>
```

## JSON Field Casing [#json-field-casing]

All response bodies use **snake_case** field names. Request bodies and query parameters also use snake_case, with request bodies additionally accepting camelCase for backward compatibility.

## Error Responses [#error-responses]

All endpoints return errors in a consistent format:

```json
{
  "error": {
    "message": "401 UNAUTHORIZED",
    "details": "Unauthorized access."
  }
}
```

Validation errors (422) include a structured `errors` array inside `details`:

```json
{
  "error": {
    "message": "422 UNPROCESSABLE ENTITY",
    "details": {
      "errors": [
        {
          "type": "missing",
          "in": "body",
          "field": "name",
          "msg": "Field required"
        }
      ]
    }
  }
}
```

Common error status codes:

| Status | Meaning |
| ------ | ------- |
| 400    | Bad request -- invalid or missing data |
| 401    | Unauthorized -- invalid or missing token |
| 404    | Not found -- resource does not exist |
| 422    | Unprocessable entity -- validation error |
| 500    | Internal server error |

## Pagination [#pagination]

Endpoints that return lists accept `limit` and `offset` query parameters and include a `pagination` object in the response:

| Parameter | Type    | Default | Min | Max |
| --------- | ------- | ------- | --- | --- |
| limit     | integer | 50      | 1   | 100 |
| offset    | integer | 0       | 0   | --  |

```json
{
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 142
  }
}
```

---

## Contacts [#contacts]

### List Contacts [#list-contacts]

Retrieve a paginated list of contacts in the workspace.

```text
GET https://api.cuedesk.com/v1/contacts
Authorization: Bearer <token>
```

**Query Parameters**

| Name              | Type   | Default      | Description |
| ----------------- | ------ | ------------ | ----------- |
| limit             | int    | 50           | Page size (1-100) |
| offset            | int    | 0            | Number of items to skip |
| order_field       | string | `created_at` | Sort field: `created_at`, `first_name`, `last_name`, `updated_at`, `external_contact_id` |
| order             | string | `desc`       | Sort direction: `asc` or `desc` |
| contact_list_uuid | UUID   | --           | Filter to contacts in a specific contact list |

**Response** `200 OK`

```json
{
  "contacts": [
    {
      "uuid": "ff0556cf-fbad-4d55-94cd-119bac9df2da",
      "workspace_uuid": "ff027904-fdbc-4c30-a533-105d1c96e1cb",
      "first_name": "Bruce",
      "last_name": "Wayne",
      "external_contact_id": "batman",
      "email": "batman@example.com",
      "phone_number": "1234567890",
      "whatsapp_id": "1234567890",
      "fb_messenger_id": "fb_12345",
      "archived": false,
      "created_at": "2025-12-10T10:31:34.353Z",
      "updated_at": "2025-12-10T10:31:34.353Z",
      "last_engaged_at": "2025-12-09T14:57:36Z",
      "custom_fields": {
        "date_of_birth": "1985-02-19",
        "subscription": "Enterprise",
        "is_verified": true,
        "num_seats": 450
      }
    }
  ],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 1
  }
}
```

---

### Search Contacts [#search-contacts]

Advanced contact search with filtering, sorting, and pagination. Supports free-text search, field-level filters (including custom fields), and custom sort orders.

All request body fields are optional. An empty body `{}` returns all contacts (equivalent to [List Contacts](#list-contacts) without filters).

```text
POST https://api.cuedesk.com/v1/contacts/search
Authorization: Bearer <token>
Content-Type: application/json
```

**Query Parameters**

| Name   | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| limit  | int  | 10      | Page size (1-100) |
| offset | int  | 0       | Number of items to skip |

**Request Body**

| Field           | Type   | Required | Description |
| --------------- | ------ | -------- | ----------- |
| search_term     | string | No       | Free-text search across default contact fields |
| advanced_search | array  | No       | Array of filter objects (see below) |
| sort_by         | object | No       | Sort configuration (see below) |

**Filter Object** (`advanced_search` items)

| Field     | Type            | Required | Default | Description |
| --------- | --------------- | -------- | ------- | ----------- |
| field     | string          | Yes      | --      | Field name to filter on (predefined or custom field key) |
| operator  | string          | Yes      | --      | Search operator (see table below) |
| value     | string or array | No       | --      | Value(s) to match. For `range`, provide `[min, max]`. Not needed for `exists`/`not_exists` |
| data_type | string          | No       | `text`  | Data type hint for custom fields: `text`, `long`, `double`, `date`, `boolean` |

**Search Operators**

| Operator       | Description |
| -------------- | ----------- |
| `equals`       | Exact match |
| `not_equals`   | Does not match |
| `contains`     | Contains substring |
| `not_contains` | Does not contain substring |
| `starts_with`  | Starts with value |
| `ends_with`    | Ends with value |
| `range`        | Within range (provide `[min, max]` as value) |
| `gt`           | Greater than |
| `lt`           | Less than |
| `gte`          | Greater than or equal to |
| `lte`          | Less than or equal to |
| `exists`       | Field has a value (no `value` needed) |
| `not_exists`   | Field has no value (no `value` needed) |

**Sort Object** (`sort_by`)

| Field      | Type   | Required | Default | Description |
| ---------- | ------ | -------- | ------- | ----------- |
| field      | string | Yes      | --      | Field name to sort by (predefined or custom field key) |
| sort_order | string | No       | `asc`   | Sort direction: `asc` or `desc` |
| data_type  | string | No       | `text`  | Data type hint for custom fields: `text`, `long`, `double`, `date`, `boolean` |

**Predefined Fields**

The following fields are predefined contact fields. Any field name not in this list is treated as a custom field key:

`first_name`, `last_name`, `email`, `phone_number`, `whatsapp_id`, `fb_messenger_id`, `external_contact_id`, `client_reference`, `archived`, `created_at`, `updated_at`, `last_engaged_at`

<Callout type="info">
When filtering or sorting on custom fields, set `data_type` to match the custom field's type for correct results. For predefined fields, `data_type` is ignored.
</Callout>

**Request Examples**

<Tabs items={["Free-text Search","Advanced Filter with Sort","Field Exists Check"]}>

<Tab value="Free-text Search">

```json
{
  "search_term": "john"
}
```

</Tab>

<Tab value="Advanced Filter with Sort">

```json
{
  "advanced_search": [
    {
      "field": "email",
      "operator": "contains",
      "value": "@example.com"
    },
    {
      "field": "num_seats",
      "operator": "gte",
      "value": "100",
      "data_type": "long"
    }
  ],
  "sort_by": {
    "field": "created_at",
    "sort_order": "desc"
  }
}
```

</Tab>

<Tab value="Field Exists Check">

```json
{
  "advanced_search": [
    {
      "field": "date_of_birth",
      "operator": "exists"
    }
  ]
}
```

</Tab>

</Tabs>

**Response** `200 OK`

```json
{
  "contacts": [
    {
      "uuid": "ff0556cf-fbad-4d55-94cd-119bac9df2da",
      "workspace_uuid": "ff027904-fdbc-4c30-a533-105d1c96e1cb",
      "first_name": "Bruce",
      "last_name": "Wayne",
      "external_contact_id": "batman",
      "email": "batman@example.com",
      "phone_number": "1234567890",
      "whatsapp_id": "1234567890",
      "fb_messenger_id": "fb_12345",
      "archived": false,
      "created_at": "2025-12-10T10:31:34.353Z",
      "updated_at": "2025-12-10T10:31:34.353Z",
      "last_engaged_at": "2025-12-09T14:57:36Z",
      "custom_fields": {
        "date_of_birth": "1985-02-19",
        "subscription": "Enterprise",
        "is_verified": true,
        "num_seats": 450
      }
    }
  ],
  "pagination": {
    "limit": 10,
    "offset": 0,
    "total": 1
  }
}
```

---

### Create a Contact [#create-a-contact]

Create a new contact in the workspace.

<Callout type="info">
At least one identifier field (`external_contact_id`, `email`, `phone_number`, `whatsapp_id`, or `fb_messenger_id`) is required.
</Callout>

<Callout type="info">
On contacts Cue creates from inbound WhatsApp messages, `whatsapp_id` holds the phone number when known. For a customer who has hidden their phone number it is `null`. See [WhatsApp usernames and BSUIDs](/webhooks/whatsapp_usernames_and_bsuids).
</Callout>

```text
POST https://api.cuedesk.com/v1/contacts
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body**

| Field               | Type   | Required | Description |
| ------------------- | ------ | -------- | ----------- |
| first_name          | string | No       | First name |
| last_name           | string | No       | Last name |
| external_contact_id | string | No*      | External system identifier |
| email               | string | No*      | Email address |
| phone_number        | string | No*      | Phone number |
| whatsapp_id         | string | No*      | WhatsApp identifier: a phone number or a BSUID |
| fb_messenger_id     | string | No*      | Facebook Messenger identifier |
| custom_fields       | object | No       | Object mapping custom field keys to values |

**Example**

```json
{
  "first_name": "Bruce",
  "last_name": "Wayne",
  "email": "batman@example.com",
  "phone_number": "1234567890",
  "custom_fields": {
    "date_of_birth": "1985-02-19"
  }
}
```

**Response** `201 Created`

Returns the created Contact object (same shape as in [List Contacts](#list-contacts)).

---

### Get a Contact [#get-a-contact]

Retrieve a single contact by UUID.

```text
GET https://api.cuedesk.com/v1/contacts/{contact_uuid}
Authorization: Bearer <token>
```

**Response** `200 OK`

Returns a single Contact object.

---

### Update a Contact [#update-a-contact]

Partially update a contact. Only include the fields you want to change.

```text
PATCH https://api.cuedesk.com/v1/contacts/{contact_uuid}
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body**

| Field               | Type   | Required | Description |
| ------------------- | ------ | -------- | ----------- |
| first_name          | string | No       | First name |
| last_name           | string | No       | Last name |
| external_contact_id | string | No       | External system identifier |
| email               | string | No       | Email address |
| phone_number        | string | No       | Phone number |
| whatsapp_id         | string | No       | WhatsApp identifier: a phone number or a BSUID |
| fb_messenger_id     | string | No       | Facebook Messenger identifier |
| custom_fields       | object | No       | Object mapping custom field keys to values |

**Example**

```json
{
  "first_name": "Bruce",
  "last_name": "Wayne-Kent",
  "custom_fields": {
    "date_of_birth": "1985-02-19"
  }
}
```

**Response** `200 OK`

Returns the updated Contact object.

---

## Bulk Create Contacts [#bulk-create-contacts]

Create up to 1,000 contacts in a single request. This operation is asynchronous -- contacts are imported via a background pipeline. The response contains a `contact_import_uuid` that can be used to track progress.

```text
POST https://api.cuedesk.com/v1/contacts/bulk
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body**

| Field             | Type   | Required | Default            | Description |
| ----------------- | ------ | -------- | ------------------ | ----------- |
| contacts          | array  | Yes      | --                 | Array of contact objects (1-1000 items) |
| contact_list_uuid | UUID   | No       | --                 | Add all contacts to this contact list |
| match_on_field    | string | No       | --                 | Field to match existing contacts: `email`, `phone_number`, or `external_contact_id` |
| merge_type        | string | No       | `create_or_update` | Merge strategy (see below) |

**Contact Object**

Each item in the `contacts` array accepts the same fields as [Create a Contact](#create-a-contact). No identifier validation is enforced at the gateway level -- the upstream import pipeline handles per-contact validation.

**Merge Strategies**

| Value              | Behaviour |
| ------------------ | --------- |
| `create_or_update` | Create new contacts or update existing ones matched by `match_on_field` |
| `create`           | Only create new contacts; skip existing matches |
| `update`           | Only update existing contacts matched by `match_on_field`; skip new ones |

**Example**

```json
{
  "contact_list_uuid": "ff234212-c92e-4f33-9350-297887ca11de",
  "contacts": [
    {
      "first_name": "Bruce",
      "last_name": "Wayne",
      "email": "batman@example.com",
      "phone_number": "1234567890",
      "custom_fields": {
        "date_of_birth": "1985-02-19"
      }
    },
    {
      "first_name": "Clark",
      "last_name": "Kent",
      "email": "clark.kent@dailyplanet.com",
      "phone_number": "9876543210"
    }
  ],
  "match_on_field": "email",
  "merge_type": "create_or_update"
}
```

**Response** `202 Accepted`

```json
{
  "contact_import_uuid": "ff28a1b2-c3d4-5e6f-7a8b-9c0d1e2f3a4b"
}
```

---

## Get Contact Import Status [#get-contact-import-status]

Check the status and progress of a bulk contact import.

```text
GET https://api.cuedesk.com/v1/contacts/bulk/{contact_import_uuid}
Authorization: Bearer <token>
```

**Response** `200 OK`

```json
{
  "contact_import_uuid": "ff28a1b2-c3d4-5e6f-7a8b-9c0d1e2f3a4b",
  "status": "completed",
  "summary": {
    "total": 2,
    "created": 1,
    "failed": 1
  }
}
```

**Status Values**

| Value      | Description |
| ---------- | ----------- |
| pending    | Import has been queued but not yet started |
| processing | Import is currently being processed |
| completed  | Import has finished (may include partial failures) |
| failed     | Import failed entirely |

**Summary Fields**

| Field   | Type | Description |
| ------- | ---- | ----------- |
| total   | int  | Total number of rows in the import |
| created | int  | Number of contacts successfully created or updated |
| failed  | int  | Number of contacts that failed to import |

---

## Contact Custom Fields [#contact-custom-fields]

Manage custom field values on individual contacts.

### List Contact Custom Fields [#list-contact-custom-fields]

Get all custom field values for a contact.

```text
GET https://api.cuedesk.com/v1/contacts/{contact_uuid}/custom-fields
Authorization: Bearer <token>
```

**Response** `200 OK`

```json
{
  "custom_fields": {
    "date_of_birth": "1985-02-19",
    "subscription": "Enterprise",
    "is_verified": true,
    "num_seats": 450
  }
}
```

Values can be strings, numbers, booleans, arrays, or objects depending on the custom field type.

---

### Add a Custom Field to a Contact [#add-a-custom-field-to-a-contact]

Set a single custom field value on a contact.

```text
POST https://api.cuedesk.com/v1/contacts/{contact_uuid}/custom-fields
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body**

| Field             | Type | Required | Description |
| ----------------- | ---- | -------- | ----------- |
| custom_field_uuid | UUID | Yes      | The custom field definition UUID |
| value             | any  | Yes      | The value to set (can be null to clear) |

**Example**

```json
{
  "custom_field_uuid": "ff24cdaf-f0a1-4db9-ba47-109f6d663544",
  "value": "1985-02-19"
}
```

**Response** `201 Created`

Returns the full `custom_fields` object for the contact (same shape as [List Contact Custom Fields](#list-contact-custom-fields)).

---

### Update Contact Custom Fields [#update-contact-custom-fields]

Update multiple custom field values on a contact at once.

```text
PATCH https://api.cuedesk.com/v1/contacts/{contact_uuid}/custom-fields
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body**

| Field         | Type   | Required | Description |
| ------------- | ------ | -------- | ----------- |
| custom_fields | object | Yes      | Object mapping custom field keys to their new values |

**Example**

```json
{
  "custom_fields": {
    "date_of_birth": "1990-01-15",
    "subscription": "Startup"
  }
}
```

**Response** `200 OK`

Returns the full `custom_fields` object for the contact.

---

### Delete a Custom Field from a Contact [#delete-a-custom-field-from-a-contact]

Remove a custom field value from a contact.

```text
DELETE https://api.cuedesk.com/v1/contacts/{contact_uuid}/custom-fields/{custom_field_uuid}
Authorization: Bearer <token>
```

**Response** `204 No Content`

No response body.

---

## Contact Lists [#contact-lists]

### List Contact Lists [#list-contact-lists]

Retrieve a paginated list of contact lists in the workspace.

```text
GET https://api.cuedesk.com/v1/contact-lists
Authorization: Bearer <token>
```

**Query Parameters**

| Name       | Type   | Default  | Description |
| ---------- | ------ | -------- | ----------- |
| limit      | int    | 50       | Page size (1-100) |
| offset     | int    | 0        | Number of items to skip |
| status     | string | `active` | Filter by status: `active` or `archived` |
| sort_order | string | `asc`    | Sort direction: `asc` or `desc` |
| sort_field | string | `name`   | Sort field: `name` or `created_at` |
| search     | string | --       | Search contact lists by name |

**Response** `200 OK`

```json
{
  "contact_lists": [
    {
      "uuid": "ff234212-c92e-4f33-9350-297887ca11de",
      "name": "VIP Customers",
      "status": "active",
      "emoji": "\u2b50",
      "workspace_uuid": "ff027904-fdbc-4c30-a533-105d1c96e1cb",
      "count": 42,
      "created_by": "ff027904-fdbc-4c30-a533-105d1c96e1cb",
      "updated_by": "ff027904-fdbc-4c30-a533-105d1c96e1cb",
      "created_at": "2025-12-10T11:08:38.055790Z",
      "updated_at": "2025-12-10T11:08:47.852679Z"
    }
  ],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 1
  }
}
```

---

### Create a Contact List [#create-a-contact-list]

Create a new contact list in the workspace.

```text
POST https://api.cuedesk.com/v1/contact-lists
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body**

| Field | Type   | Required | Description |
| ----- | ------ | -------- | ----------- |
| name  | string | Yes      | Contact list name |
| emoji | string | No       | Unicode emoji |

**Example**

```json
{
  "name": "VIP Customers",
  "emoji": "\u2b50"
}
```

**Response** `201 Created`

Returns the created ContactList object (same shape as in [List Contact Lists](#list-contact-lists)).

---

### Get a Contact List [#get-a-contact-list]

Retrieve a single contact list by UUID.

```text
GET https://api.cuedesk.com/v1/contact-lists/{contact_list_uuid}
Authorization: Bearer <token>
```

**Response** `200 OK`

Returns a single ContactList object.

---

### Update a Contact List [#update-a-contact-list]

Partially update a contact list. Only include the fields you want to change.

```text
PATCH https://api.cuedesk.com/v1/contact-lists/{contact_list_uuid}
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body**

| Field | Type   | Required | Description |
| ----- | ------ | -------- | ----------- |
| name  | string | No       | Contact list name |
| emoji | string | No       | Unicode emoji |

**Example**

```json
{
  "name": "Premium VIP Customers",
  "emoji": "\ud83d\udc8e"
}
```

**Response** `200 OK`

Returns the updated ContactList object.

---

### Delete a Contact List [#delete-a-contact-list]

Delete a contact list.

```text
DELETE https://api.cuedesk.com/v1/contact-lists/{contact_list_uuid}
Authorization: Bearer <token>
```

**Response** `204 No Content`

No response body.

---

## Contact List Membership [#contact-list-membership]

Manage which contacts belong to which contact lists.

### List Contacts in a Contact List [#list-contacts-in-a-contact-list]

Retrieve a paginated list of contacts that belong to a contact list.

```text
GET https://api.cuedesk.com/v1/contact-lists/{contact_list_uuid}/contacts
Authorization: Bearer <token>
```

**Query Parameters**

| Name             | Type    | Default | Description |
| ---------------- | ------- | ------- | ----------- |
| limit            | int     | 50      | Page size (1-100) |
| offset           | int     | 0       | Number of items to skip |
| include_archived | boolean | false   | Include archived contacts |

**Response** `200 OK`

Returns a `contacts` array and `pagination` object (same shape as [List Contacts](#list-contacts)).

---

### Add Contacts to a Contact List [#add-contacts-to-a-contact-list]

Add one or more contacts to a contact list.

```text
POST https://api.cuedesk.com/v1/contact-lists/{contact_list_uuid}/contacts
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body**

| Field         | Type        | Required | Description |
| ------------- | ----------- | -------- | ----------- |
| contact_uuids | array[UUID] | Yes      | UUIDs of contacts to add (1-100 items) |

**Example**

```json
{
  "contact_uuids": [
    "ff0556cf-fbad-4d55-94cd-119bac9df2da",
    "ff1667df-acde-4e66-a5de-220cbe916e2b"
  ]
}
```

**Response** `201 Created`

```json
{
  "message": "Contacts successfully added to contact list.",
  "added": [
    "ff0556cf-fbad-4d55-94cd-119bac9df2da",
    "ff1667df-acde-4e66-a5de-220cbe916e2b"
  ],
  "warnings": []
}
```

| Field    | Type        | Description |
| -------- | ----------- | ----------- |
| message  | string      | Success message |
| added    | array[UUID] | UUIDs of contacts that were successfully added |
| warnings | array       | List of warning objects with `uuid` and `message` for contacts that could not be added |

---

### Remove Contacts from a Contact List [#remove-contacts-from-a-contact-list]

Remove one or more contacts from a contact list.

```text
DELETE https://api.cuedesk.com/v1/contact-lists/{contact_list_uuid}/contacts
Authorization: Bearer <token>
```

**Request Body**

| Field         | Type        | Required | Description |
| ------------- | ----------- | -------- | ----------- |
| contact_uuids | array[UUID] | Yes      | UUIDs of contacts to remove (1-100 items) |

**Example**

```json
{
  "contact_uuids": [
    "ff0556cf-fbad-4d55-94cd-119bac9df2da"
  ]
}
```

**Response** `204 No Content`

No response body.

---

### Get Contact Lists for a Contact [#get-contact-lists-for-a-contact]

List all contact lists that contain a specific contact.

```text
GET https://api.cuedesk.com/v1/contacts/{contact_uuid}/contact-lists
Authorization: Bearer <token>
```

**Query Parameters**

| Name   | Type   | Default | Description |
| ------ | ------ | ------- | ----------- |
| limit  | int    | 50      | Page size (1-100) |
| offset | int    | 0       | Number of items to skip |
| status | string | --      | Filter by status: `active` or `archived` |

**Response** `200 OK`

Returns a `contact_lists` array and `pagination` object (same shape as [List Contact Lists](#list-contact-lists)).

---

## Custom Fields [#custom-fields]

Manage workspace-level custom field definitions. These define the schema for custom fields that can be assigned to contacts (see [Contact Custom Fields](#contact-custom-fields) for managing values on individual contacts).

**Field Types**

Custom fields support the following types:

| Type                | Description |
| ------------------- | ----------- |
| `text`              | Single-line text |
| `number:integer`    | Whole number |
| `number:decimal`    | Decimal number |
| `number:percentage` | Percentage value |
| `checkbox`          | Boolean checkbox |
| `date`              | Date only |
| `date:date_time`    | Date and time |
| `date:time`         | Time only |
| `date:relative`     | Relative date |
| `select`            | Single-select dropdown |
| `multiselect`       | Multi-select dropdown |
| `user`              | User reference |

<Callout type="info">
For `select` and `multiselect` types, `select_options` must be provided with at least one option.
</Callout>

---

### List Custom Fields [#list-custom-fields]

Retrieve a paginated list of custom field definitions in the workspace.

```text
GET https://api.cuedesk.com/v1/custom-fields
Authorization: Bearer <token>
```

**Query Parameters**

| Name   | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| limit  | int  | 50      | Page size (1-100) |
| offset | int  | 0       | Number of items to skip |

**Response** `200 OK`

```json
{
  "custom_fields": [
    {
      "uuid": "ff24cdaf-f0a1-4db9-ba47-109f6d663544",
      "workspace_uuid": "ff027904-fdbc-4c30-a533-105d1c96e1cb",
      "name": "Revenue",
      "icon": null,
      "field_type": "number:integer",
      "field_key": "revenue",
      "status": "active",
      "created_at": "2025-12-10T10:31:34.353Z",
      "updated_at": "2025-12-10T10:31:34.353Z",
      "archived_at": null,
      "editable": true,
      "sensitive": false
    },
    {
      "uuid": "ff35debc-a1b2-4c3d-be58-210e7d774655",
      "workspace_uuid": "ff027904-fdbc-4c30-a533-105d1c96e1cb",
      "name": "Priority",
      "icon": "star",
      "field_type": "select",
      "field_key": "priority",
      "status": "active",
      "created_at": "2025-12-11T08:15:00.000Z",
      "updated_at": "2025-12-11T08:15:00.000Z",
      "archived_at": null,
      "editable": true,
      "sensitive": false,
      "select_options": [
        { "uuid": "opt-uuid-1", "display_label": "Low", "field_key": "low", "archived": false },
        { "uuid": "opt-uuid-2", "display_label": "Medium", "field_key": "medium", "archived": false },
        { "uuid": "opt-uuid-3", "display_label": "High", "field_key": "high", "archived": false }
      ]
    }
  ],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 2
  }
}
```

<Callout type="info">
The `select_options` field is only present on custom fields with `field_type` of `select` or `multiselect`. It is omitted entirely for all other field types.
</Callout>

---

### Create a Custom Field [#create-a-custom-field]

Create a new custom field definition in the workspace.

```text
POST https://api.cuedesk.com/v1/custom-fields
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body**

| Field          | Type          | Required | Default | Description |
| -------------- | ------------- | -------- | ------- | ----------- |
| name           | string        | Yes      | --      | Field name (3-250 characters) |
| field_type     | string        | Yes      | --      | One of the [field types](#custom-fields) listed above |
| field_key      | string        | No       | `null`  | Unique key identifier for the custom field |
| editable       | boolean       | No       | `true`  | Whether the field value can be edited on contacts |
| sensitive      | boolean       | No       | `false` | Whether the field contains sensitive data |
| icon           | string        | No       | `null`  | Icon identifier |
| select_options | array[string] | No       | `null`  | List of option labels |

<Callout type="warn">
If `field_key` is omitted, a value will be generated from `name`. The `field_key` **cannot be changed after creation**.
</Callout>

<Callout type="info">
`select_options` is required and must be non-empty when `field_type` is `select` or `multiselect`.
</Callout>

**Example**

```json
{
  "name": "Priority",
  "field_type": "select",
  "field_key": "priority",
  "editable": true,
  "icon": "star",
  "select_options": ["Low", "Medium", "High"]
}
```

**Response** `201 Created`

Returns the created CustomField object (same shape as in [List Custom Fields](#list-custom-fields)).

---

### Get a Custom Field [#get-a-custom-field]

Retrieve a single custom field definition by UUID.

```text
GET https://api.cuedesk.com/v1/custom-fields/{custom_field_uuid}
Authorization: Bearer <token>
```

**Response** `200 OK`

Returns a single CustomField object.

---

### Update a Custom Field [#update-a-custom-field]

Update a custom field definition. All fields in the request body are required -- this endpoint expects the full object, not a partial update.

```text
PATCH https://api.cuedesk.com/v1/custom-fields/{custom_field_uuid}
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body**

| Field          | Type          | Required | Description |
| -------------- | ------------- | -------- | ----------- |
| name           | string        | Yes      | Field name (3-250 characters) |
| field_type     | string        | Yes      | One of the [field types](#custom-fields) listed above |
| editable       | boolean       | Yes      | Whether the field value can be edited on contacts |
| sensitive      | boolean       | Yes      | Whether the field contains sensitive data |
| icon           | string        | Yes      | Icon identifier (use `null` to clear) |
| select_options | array[string] | No       | List of option labels |

<Callout type="info">
`select_options` is required and must be non-empty when `field_type` is `select` or `multiselect`.
</Callout>

**Example**

```json
{
  "name": "Updated Priority",
  "field_type": "select",
  "editable": true,
  "sensitive": false,
  "icon": "star",
  "select_options": ["Low", "Medium", "High", "Critical"]
}
```

**Response** `200 OK`

Returns the updated CustomField object.

---

### Delete a Custom Field [#delete-a-custom-field]

Delete a custom field definition.

```text
DELETE https://api.cuedesk.com/v1/custom-fields/{custom_field_uuid}
Authorization: Bearer <token>
```

**Response** `204 No Content`

No response body.

---

## Contact Notes [#contact-notes]

Manage notes attached to individual contacts.

### List Contact Notes [#list-contact-notes]

Retrieve a paginated list of notes for a contact.

```text
GET https://api.cuedesk.com/v1/contacts/{contact_uuid}/notes
Authorization: Bearer <token>
```

**Query Parameters**

| Name   | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| limit  | int  | 50      | Page size (1-100) |
| offset | int  | 0       | Number of items to skip |

**Response** `200 OK`

```json
{
  "notes": [
    {
      "uuid": "ff46efcd-b2c3-4d4e-cf69-321f8e885766",
      "title": "Meeting Notes",
      "content": {
        "text": "Discussed project timeline and deliverables."
      },
      "created_at": "2025-12-10T09:00:00.000Z",
      "updated_at": "2025-12-10T09:30:00.000Z",
      "archived_at": null,
      "status": "active"
    }
  ],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 1
  }
}
```

---

### Create a Contact Note [#create-a-contact-note]

Create a new note on a contact.

```text
POST https://api.cuedesk.com/v1/contacts/{contact_uuid}/notes
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body**

| Field        | Type   | Required | Description |
| ------------ | ------ | -------- | ----------- |
| title        | string | No       | Note title |
| content      | object | Yes      | Note content object |
| content.text | string | Yes      | Text body of the note |

**Example**

```json
{
  "title": "Meeting Notes",
  "content": {
    "text": "Discussed project timeline and deliverables."
  }
}
```

**Response** `201 Created`

Returns the created Note object (same shape as in [List Contact Notes](#list-contact-notes)).

---

### Get a Contact Note [#get-a-contact-note]

Retrieve a single note by UUID.

```text
GET https://api.cuedesk.com/v1/contacts/{contact_uuid}/notes/{note_uuid}
Authorization: Bearer <token>
```

**Response** `200 OK`

Returns a single Note object.

---

### Update a Contact Note [#update-a-contact-note]

Replace a note entirely. This is a full replacement -- all fields in the request body are required.

```text
PUT https://api.cuedesk.com/v1/contacts/{contact_uuid}/notes/{note_uuid}
Authorization: Bearer <token>
Content-Type: application/json
```

**Request Body**

| Field        | Type   | Required | Description |
| ------------ | ------ | -------- | ----------- |
| title        | string | Yes      | Note title |
| content      | object | Yes      | Note content object |
| content.text | string | Yes      | Text body of the note |

**Example**

```json
{
  "title": "Updated Meeting Notes",
  "content": {
    "text": "Revised action items from the meeting."
  }
}
```

**Response** `200 OK`

Returns the updated Note object.

---

### Delete a Contact Note [#delete-a-contact-note]

Delete a note from a contact.

```text
DELETE https://api.cuedesk.com/v1/contacts/{contact_uuid}/notes/{note_uuid}
Authorization: Bearer <token>
```

**Response** `204 No Content`

No response body.
