Inkbox

> # Documentation index
> Fetch the complete documentation index at: https://inkbox.ai/sitemap.xml
> Use this file to discover all available pages before exploring further.

---

# Manage Contacts
description: Create, list, look up, update, and delete contacts

---


# Manage Contacts

CRUD and reverse-lookup endpoints for the organization's contact directory. All contacts are org-scoped; the caller's organization is derived from the auth context.

---

## Create contact `POST`


Create a new contact. At least one of `given_name`, `family_name`, `company_name`, or `preferred_name` must be non-empty (the vCard FN rule). If `preferred_name` is omitted it is synthesized from the other name fields.

### Request body

| Field | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `preferred_name` | string \| null | No | Display name (max 255 chars). Synthesized if omitted. |
| `name_prefix` | string \| null | No | e.g. `"Dr."` (max 32) |
| `given_name` | string \| null | No | First name (max 128) |
| `middle_name` | string \| null | No | Middle name (max 128) |
| `family_name` | string \| null | No | Last name (max 128) |
| `name_suffix` | string \| null | No | e.g. `"Jr."` (max 32) |
| `company_name` | string \| null | No | Company (max 255) |
| `job_title` | string \| null | No | Title (max 255) |
| `birthday` | string \| null | No | ISO `YYYY-MM-DD` |
| `notes` | string \| null | No | Free-text note on the contact |
| `emails` | object[] | No | Up to 50 items. See [Contact object](#contact-object). |
| `phones` | object[] | No | Up to 50 items (E.164 values) |
| `websites` | object[] | No | Up to 25 items |
| `dates` | object[] | No | Up to 25 non-birthday dates |
| `addresses` | object[] | No | Up to 10 postal addresses |
| `custom_fields` | object[] | No | Up to 50 free-form label/value pairs |
| `access_identity_ids` | UUID[] \| null | No | Controls who can see the new contact. **Omit** (or `null`) → wildcard (every agent sees it, default). **`[]`** → nobody but admins/humans see it. **Explicit list** → only these identities see it. |

### Request example

```json
{
    "given_name": "Jane",
    "family_name": "Doe",
    "company_name": "Acme Inc.",
    "emails": [
      { "value": "jane@acme.example", "label": "work", "is_primary": true }
    ],
    "phones": [
      { "value_e164": "+15551234567", "label": "mobile" }
    ]
}
```

### Response (201)

Returns the created contact with an inlined `access` array (see [Contact object](#contact-object)).

### Error responses

| Status | Description |
| :--- | :--- |
| 400 | vCard FN rule violated — no name fields provided |
| 404 | One or more `access_identity_ids` do not resolve to an active identity in your org |
| 422 | Invalid email, non-E.164 phone, duplicate email/phone, or more than one `is_primary` |

### Code examples

**cURL**

```bash
curl -X POST "https://inkbox.ai/api/v1/contacts" \\
    -H "X-API-Key: YOUR_API_KEY" \\
    -H "Content-Type: application/json" \\
    -d '{
      "given_name": "Jane",
      "family_name": "Doe",
      "emails": [{"value": "jane@acme.example", "label": "work"}]
    }'
```

**JavaScript**

```javascript
const response = await fetch("https://inkbox.ai/api/v1/contacts", {
    method: "POST",
    headers: {
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    body: JSON.stringify({
        given_name: "Jane",
        family_name: "Doe",
        emails: [{ value: "jane@acme.example", label: "work" }],
    }),
});
const contact = await response.json();
```

**Python**

```python
import requests

response = requests.post(
    "https://inkbox.ai/api/v1/contacts",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "given_name": "Jane",
        "family_name": "Doe",
        "emails": [{"value": "jane@acme.example", "label": "work"}],
    },
)
contact = response.json()
```

---

## List contacts `GET`


List contacts visible to the caller. Scoped agents only see contacts they have access to; admins and Clerk JWT users see every contact in the org.

### Query parameters

| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `q` | string | — | Case-insensitive substring match over name, company, job title, and the `notes` field. Max 100 chars. |
| `order` | string | `name` | `"name"` (preferred_name ascending) or `"recent"` (created_at descending) |
| `limit` | integer | 50 | Results per page (1–200) |
| `offset` | integer | 0 | Offset for pagination |

### Response (200)

Returns an array of contact objects, each with an inlined `access` array.

### Code examples

**cURL**

```bash
curl -X GET "https://inkbox.ai/api/v1/contacts?q=acme&limit=25" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const response = await fetch(
    "https://inkbox.ai/api/v1/contacts?q=acme&limit=25",
    { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const contacts = await response.json();
```

**Python**

```python
import requests

response = requests.get(
    "https://inkbox.ai/api/v1/contacts",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"q": "acme", "limit": 25},
)
contacts = response.json()
```

---

## Lookup contact `GET`


Reverse-lookup contacts by a single field. Useful for routing inbound email or phone calls to a known contact.

### Query parameters

Exactly **one** of the following must be provided:

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `email` | string | Exact match against a stored email (canonicalized to lowercase) |
| `email_contains` | string | Substring match on stored email values (case-insensitive) |
| `email_domain` | string | Suffix match on `@<domain>`. Leading `@` tolerated (max 253 chars) |
| `phone` | string | Exact match against a stored E.164 phone number |
| `phone_contains` | string | Substring match on stored E.164 phone values |

### Response (200)

Returns an array of contact objects (up to 200). An empty array means no match.

### Error responses

| Status | Description |
| :--- | :--- |
| 400 | Zero or more than one filter provided; or `phone` is not a valid E.164 number |

### Code examples

**cURL**

```bash
curl -X GET "https://inkbox.ai/api/v1/contacts/lookup?email_domain=acme.example" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const response = await fetch(
    "https://inkbox.ai/api/v1/contacts/lookup?" +
        new URLSearchParams({ email_domain: "acme.example" }),
    { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const matches = await response.json();
```

**Python**

```python
import requests

response = requests.get(
    "https://inkbox.ai/api/v1/contacts/lookup",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"email_domain": "acme.example"},
)
matches = response.json()
```

---

## Get contact `GET`


Fetch a single contact by its UUID.

### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `contact_id` | UUID | Unique contact identifier |

### Code examples

**cURL**

```bash
curl -X GET "https://inkbox.ai/api/v1/contacts/CONTACT_ID" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const response = await fetch(
    `https://inkbox.ai/api/v1/contacts/${contactId}`,
    { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const contact = await response.json();
```

**Python**

```python
import requests

response = requests.get(
    f"https://inkbox.ai/api/v1/contacts/{contact_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
contact = response.json()
```

---

## Update contact `PATCH`


Update a contact using JSON-merge-patch semantics:

- **Omit** a field to leave it unchanged
- Send a value to set it
- Send `null` on a nullable scalar to clear it
- Send a list to **replace** the stored list wholesale (no per-item merge)

After applying the patch, the server re-runs the FN rule against the merged state — if the update would leave every name field empty, the request 400s.

### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `contact_id` | UUID | Unique contact identifier |

### Request body

Any subset of the [Create contact](#create-contact) body fields. `access_identity_ids` cannot be changed via PATCH — use the [Access control](/docs/api/contacts/access) endpoints instead.

### Code examples

**cURL**

```bash
curl -X PATCH "https://inkbox.ai/api/v1/contacts/CONTACT_ID" \\
    -H "X-API-Key: YOUR_API_KEY" \\
    -H "Content-Type: application/json" \\
    -d '{"job_title": "VP Engineering"}'
```

**JavaScript**

```javascript
const response = await fetch(
    `https://inkbox.ai/api/v1/contacts/${contactId}`,
    {
        method: "PATCH",
        headers: {
            "X-API-Key": "YOUR_API_KEY",
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ job_title: "VP Engineering" }),
    }
);
const contact = await response.json();
```

**Python**

```python
import requests

response = requests.patch(
    f"https://inkbox.ai/api/v1/contacts/{contact_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"job_title": "VP Engineering"},
)
contact = response.json()
```

---

## Delete contact `DELETE`


Delete a contact. Returns `204 No Content` on success.

### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `contact_id` | UUID | Unique contact identifier |

### Code examples

**cURL**

```bash
curl -X DELETE "https://inkbox.ai/api/v1/contacts/CONTACT_ID" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
await fetch(
    `https://inkbox.ai/api/v1/contacts/${contactId}`,
    {
        method: "DELETE",
        headers: { "X-API-Key": "YOUR_API_KEY" },
    }
);
```

**Python**

```python
import requests

requests.delete(
    f"https://inkbox.ai/api/v1/contacts/{contact_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
```

---

## Contact object

### Top-level fields

| Field | Type | Description |
| :--- | :--- | :--- |
| `id` | UUID | Unique contact identifier |
| `organization_id` | string | Owning organization |
| `preferred_name` | string \| null | Display name — always non-empty in responses |
| `name_prefix` | string \| null | |
| `given_name` | string \| null | |
| `middle_name` | string \| null | |
| `family_name` | string \| null | |
| `name_suffix` | string \| null | |
| `company_name` | string \| null | |
| `job_title` | string \| null | |
| `birthday` | string \| null | ISO `YYYY-MM-DD` |
| `notes` | string \| null | Free-text note |
| `emails` | object[] | See [Email item](#email-item) |
| `phones` | object[] | See [Phone item](#phone-item) |
| `websites` | object[] | See [Website item](#website-item) |
| `dates` | object[] | See [Date item](#date-item) |
| `addresses` | object[] | See [Address item](#address-item) |
| `custom_fields` | object[] | See [Custom field item](#custom-field-item) |
| `status` | string | `active` or `deleted` |
| `created_at` | string | ISO 8601 |
| `updated_at` | string | ISO 8601 |
| `access` | object[] | Inlined [access rules](/docs/api/contacts/access#access-rule-object) — either a single wildcard row (`identity_id: null`), an explicit per-identity list, or empty |

### Email item

| Field | Type | Description |
| :--- | :--- | :--- |
| `value` | string | Email address (stored lowercased) |
| `label` | string \| null | e.g. `"work"`, `"home"` (max 64) |
| `is_primary` | boolean | At most one email per contact is primary |

### Phone item

| Field | Type | Description |
| :--- | :--- | :--- |
| `value_e164` | string | E.164 phone number (e.g. `+15551234567`) |
| `label` | string \| null | e.g. `"mobile"`, `"work"` (max 64) |
| `is_primary` | boolean | At most one phone per contact is primary |

### Website item

| Field | Type | Description |
| :--- | :--- | :--- |
| `url` | string | HTTP/HTTPS URL (max 2048) |
| `label` | string \| null | e.g. `"personal"`, `"linkedin"` (max 64) |

### Date item

| Field | Type | Description |
| :--- | :--- | :--- |
| `date` | string | ISO `YYYY-MM-DD` |
| `label` | string | Required free-string label (e.g. `"anniversary"`) |

### Address item

All fields are optional free text.

| Field | Type | Description |
| :--- | :--- | :--- |
| `street` | string \| null | Street line(s) |
| `city` | string \| null | |
| `region` | string \| null | State, province, or region |
| `postal` | string \| null | Postal or ZIP code |
| `country` | string \| null | Country name or ISO code |
| `label` | string \| null | e.g. `"home"`, `"work"` |

### Custom field item

| Field | Type | Description |
| :--- | :--- | :--- |
| `label` | string | Field key (max 128) |
| `value` | string | Field value (max 1024) |
