> For the complete documentation index, see [llms.txt](https://docs.zero.inc/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zero.inc/features/api/contacts.md).

# Contacts

Manage contacts within workspaces

## List contacts

> Returns contacts the user has access to. Filter by workspace using the \`where\` parameter: \`{"workspaceId": "\<WORKSPACE\_UUID>"}\`.<br>

````json
{"openapi":"3.0.3","info":{"title":"Zero API","version":"1.12.0"},"tags":[{"name":"Contacts","description":"Manage contacts within workspaces"}],"servers":[{"url":"https://api.zero.inc","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"All API requests require a Bearer token in the Authorization header. Create an API key from [Workspace Settings → API keys](https://app.zero.inc/settings/workspace/api)"}},"parameters":{"fields":{"name":"fields","in":"query","description":"Comma-separated list of fields to return. Defaults to all fields.","schema":{"type":"string"}},"where":{"name":"where","in":"query","description":"JSON-encoded filter object. All top-level conditions are combined with AND logic. Use `$or` for OR logic.\n\nThe available operators depend on the **data type** of the field you are filtering on.\n\n---\n\n## String fields\nFields like `name`, `domain`, `description`, `linkedin`, `source`, `externalId`, `location`.\n\n| Operator | Description | Example |\n|----------|-------------|---------|\n| *(exact)* | Exact match | `{\"name\": \"Linear\"}` |\n| `$eq` | Explicit exact match | `{\"name\": {\"$eq\": \"Linear\"}}` |\n| `$not` | Not equal | `{\"source\": {\"$not\": \"import\"}}` |\n| `$in` | Matches any value in array | `{\"domain\": {\"$in\": [\"linear.app\", \"granola.so\"]}}` |\n| `$notIn` | Matches none of the values | `{\"source\": {\"$notIn\": [\"import\", \"api\"]}}` |\n| `$contains` | Case-insensitive word-boundary substring match | `{\"name\": {\"$contains\": \"YC\"}}` |\n| `$notContains` | Does not contain | `{\"name\": {\"$notContains\": \"Test\"}}` |\n| `$containsAny` | Contains any of the given strings | `{\"name\": {\"$containsAny\": [\"YC\", \"Techstars\"]}}` |\n| `$startsWith` | Starts with prefix | `{\"domain\": {\"$startsWith\": \"app.\"}}` |\n| `$endsWith` | Ends with suffix | `{\"email\": {\"$endsWith\": \"@zero.inc\"}}` |\n| `$exists` | Field is present and truthy | `{\"linkedin\": {\"$exists\": true}}` |\n| `$notExists` | Field is absent, null, or empty | `{\"linkedin\": {\"$notExists\": true}}` |\n\n---\n\n## Number fields\nFields like `value`, `confidence`.\n\n| Operator | Description | Example |\n|----------|-------------|---------|\n| *(exact)* | Exact match | `{\"value\": 5000}` |\n| `$eq` | Explicit exact match | `{\"value\": {\"$eq\": 5000}}` |\n| `$not` | Not equal | `{\"value\": {\"$not\": 0}}` |\n| `$gt` | Greater than | `{\"value\": {\"$gt\": 10000}}` |\n| `$gte` | Greater than or equal | `{\"value\": {\"$gte\": 5000}}` |\n| `$lt` | Less than | `{\"value\": {\"$lt\": 10000}}` |\n| `$lte` | Less than or equal | `{\"value\": {\"$lte\": 50000}}` |\n| `$in` | Matches any value in array | `{\"confidence\": {\"$in\": [0.25, 0.5, 0.75]}}` |\n| `$notIn` | Matches none of the values | `{\"confidence\": {\"$notIn\": [0, 1]}}` |\n| `$exists` | Field is present and truthy | `{\"value\": {\"$exists\": true}}` |\n| `$notExists` | Field is absent, null, or zero | `{\"value\": {\"$notExists\": true}}` |\n\nMultiple operators can be combined on one field:\n```json\n{\"value\": {\"$gte\": 5000, \"$lt\": 10000}}\n```\n\n---\n\n## Date fields\nFields like `closeDate`, `startDate`, `endDate`, `createdAt`, `updatedAt`.\n\nValues can be ISO 8601 strings (`\"2026-01-01\"`, `\"2026-01-01T00:00:00Z\"`) or **relative time macros**.\n\n**Relative time macros:** `+Nd` / `-Nd` (days), `+Nw` / `-Nw` (weeks), `+Nm` / `-Nm` (months), `+Ny` / `-Ny` (years), `+Nh` / `-Nh` (hours), `+Ns` / `-Ns` (seconds), `now()`.\n\n| Operator | Description | Example |\n|----------|-------------|---------|\n| `$gte` | On or after | `{\"closeDate\": {\"$gte\": \"2026-01-01\"}}` |\n| `$lte` | On or before | `{\"closeDate\": {\"$lte\": \"2026-03-31\"}}` |\n| `$gt` | After | `{\"createdAt\": {\"$gt\": \"2026-01-01T00:00:00Z\"}}` |\n| `$lt` | Before | `{\"createdAt\": {\"$lt\": \"now()\"}}` |\n| `$date` | Exact date match (compares date portion only) | `{\"closeDate\": {\"$date\": \"2026-01-15\"}}` |\n| `$exists` | Field is present and truthy | `{\"closeDate\": {\"$exists\": true}}` |\n| `$notExists` | Field is absent or null | `{\"closeDate\": {\"$notExists\": true}}` |\n\nDate range example:\n```json\n{\"closeDate\": {\"$gte\": \"2026-01-01\", \"$lte\": \"2026-03-31\"}}\n```\nRelative date example (closing in next 30 days):\n```json\n{\"closeDate\": {\"$gte\": \"now()\", \"$lte\": \"+30d\"}}\n```\n\n---\n\n## Array fields\nFields like `listIds`, `ownerIds`, `contactIds`.\n\n| Operator | Description | Example |\n|----------|-------------|---------|\n| `$includes` | Array contains the given value (use this — bare exact match is not supported) | `{\"listIds\": {\"$includes\": \"<LIST_UUID>\"}}` |\n| `$notIncludes` | Array does not contain the given value | `{\"ownerIds\": {\"$notIncludes\": \"<USER_UUID>\"}}` |\n| `$overlaps` | Array contains at least one of the given values | `{\"ownerIds\": {\"$overlaps\": [\"<UUID_1>\", \"<UUID_2>\"]}}` |\n| `$notOverlaps` | Array contains none of the given values | `{\"listIds\": {\"$notOverlaps\": [\"<UUID_1>\", \"<UUID_2>\"]}}` |\n| `$all` | Array contains all of the given values | `{\"listIds\": {\"$all\": [\"<UUID_1>\", \"<UUID_2>\"]}}` |\n| `$length` | Filter by array length (supports nested operators) | `{\"contactIds\": {\"$length\": {\"$gte\": 2}}}` |\n| `$exists` | Field is present and non-empty | `{\"ownerIds\": {\"$exists\": true}}` |\n| `$notExists` | Field is absent or empty | `{\"ownerIds\": {\"$notExists\": true}}` |\n\n---\n\n## UUID / ID fields\nFields like `id`, `workspaceId`, `companyId`, `stage`.\n\n| Operator | Description | Example |\n|----------|-------------|---------|\n| *(exact)* | Exact match | `{\"stage\": \"<PIPELINE_STAGE_UUID>\"}` |\n| `$in` | Matches any value in array | `{\"stage\": {\"$in\": [\"<UUID_1>\", \"<UUID_2>\"]}}` |\n| `$notIn` | Matches none of the values | `{\"stage\": {\"$notIn\": [\"<UUID_1>\"]}}` |\n| `$not` | Not equal | `{\"stage\": {\"$not\": \"<UUID>\"}}` |\n\n---\n\n## Boolean fields\nFields like `archived`.\n\n| Operator | Description | Example |\n|----------|-------------|---------|\n| *(exact)* | Exact match | `{\"archived\": false}` |\n| `$not` | Not equal | `{\"archived\": {\"$not\": true}}` |\n\n---\n\n## Logical operators\nThese work across all data types.\n\n`$or` — matches if **any** sub-condition is true:\n```json\n{\n  \"workspaceId\": \"<WORKSPACE_UUID>\",\n  \"$or\": [\n    {\"ownerIds\": {\"$overlaps\": [\"<USER_UUID_1>\", \"<USER_UUID_2>\"]}},\n    {\"closeDate\": {\"$gte\": \"2026-01-01\"}}\n  ]\n}\n```\n\n`$and` — matches if **all** sub-conditions are true (useful when you need multiple conditions on the same field):\n```json\n{\n  \"$and\": [\n    {\"name\": {\"$contains\": \"Enterprise\"}},\n    {\"name\": {\"$notContains\": \"Test\"}}\n  ]\n}\n```\n\n---\n\n## Dot-notation (relation filtering)\nUse dot-syntax to filter records based on properties of related objects:\n```json\n{\"company.name\": \"Linear\"}\n{\"company.domain\": {\"$in\": [\"linear.app\", \"granola.so\"]}}\n{\"company.location.city\": \"San Francisco\"}\n{\"companyProfile.categories\": {\"$overlaps\": [\"Sales\", \"Marketing\"]}}\n```\nAll operators available for the target field's data type can be used with dot-notation.\n\n---\n\n## Custom property filtering\nCustom properties are stored in the `custom` object on records, keyed by UUID. Use `GET /api/columns` to discover the available custom property IDs, types, and options for a workspace.\n\nFilter on custom properties using `custom.<COLUMN_ID>` with operators appropriate for the column's type.\n\n> **Note:** For `select` and `multiselect` columns, option values are UUIDs (the `key` field from the column's `options` array). Use the UUID, not the human-readable name.\n\n```json\n// Text custom property\n{\"custom.54e1ca7d-69c3-4b77-8266-8085b5834116\": {\"$contains\": \"enterprise\"}}\n\n// Select custom property (use the option key UUID)\n{\"custom.a1b2c3d4-e5f6-7890-abcd-ef1234567890\": \"3e839b5c-b311-4887-b2da-727d2d75cdd6\"}\n\n// Multi-select custom property (use option key UUIDs)\n{\"custom.b2c3d4e5-f6a7-8901-bcde-f12345678901\": {\"$overlaps\": [\"a1b1c1d1-e1f1-1111-aaaa-111111111111\", \"b2b2c2d2-e2f2-2222-bbbb-222222222222\"]}}\n\n// Currency/number custom property\n{\"custom.c3d4e5f6-a7b8-9012-cdef-123456789012\": {\"$gte\": 100000}}\n\n// Date custom property\n{\"custom.d4e5f6a7-b8c9-0123-defa-234567890123\": {\"$gte\": \"2026-01-01\"}}\n\n// Boolean custom property\n{\"custom.e5f6a7b8-c9d0-1234-efab-345678901234\": true}\n\n// Check if custom property has a value\n{\"custom.f6a7b8c9-d0e1-2345-fabc-456789012345\": {\"$exists\": true}}\n```\n\n---\n\n## Complex example\nAll top-level keys are ANDed together:\n```json\n{\n  \"name\": {\"$contains\": \"Zero\"},\n  \"location.city\": \"Helsinki\",\n  \"location.country\": {\"$in\": [\"United Kingdom\", \"Germany\", \"Sweden\"]},\n  \"value\": {\"$gte\": 10000},\n  \"closeDate\": {\"$gte\": \"2026-01-01\", \"$lte\": \"2026-03-31\"},\n  \"ownerIds\": {\"$includes\": \"<USER_UUID>\"},\n  \"stage\": {\"$in\": [\"<UUID_1>\", \"<UUID_2>\"]},\n  \"lastActivity\": {\"$exists\": true},\n  \"companyProfile.categories\": {\"$overlaps\": [\"Sales\", \"Marketing\"]},\n  \"custom.54e1ca7d-69c3-4b77-8266-8085b5834116\": {\"$contains\": \"enterprise\"}\n}\n```\n","schema":{"type":"string"}},"limit":{"name":"limit","in":"query","description":"Maximum number of records to return","schema":{"type":"integer","default":100}},"offset":{"name":"offset","in":"query","description":"Pagination offset","schema":{"type":"integer","default":0}},"orderBy":{"name":"orderBy","in":"query","description":"JSON string for sort order","schema":{"type":"string"}}},"schemas":{"Contact":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"workspaceId":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"title":{"type":"string"},"phone":{"type":"string"},"linkedin":{"type":"string"},"x":{"type":"string","description":"X (formerly Twitter) handle"},"facebook":{"type":"string"},"github":{"type":"string"},"avatar":{"type":"string","format":"uri"},"location":{"type":"object","description":"Geographic location of the contact. Stored as a structured object, not a plain string.\n","properties":{"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"continent":{"type":"string"},"countryCode":{"type":"string"},"stateCode":{"type":"string"},"coordinates":{"type":"object","properties":{"lat":{"type":"number"},"lng":{"type":"number"}}}}},"type":{"type":"string"},"custom":{"type":"object","description":"Custom property values, keyed by column UUID. Use `GET /api/columns` to discover available custom property IDs and types. When updating via PATCH, always use dot-notation (e.g. `\"custom.<COLUMN_ID>\": \"value\"`) to avoid overwriting other custom properties.\n"},"listIds":{"type":"array","items":{"type":"string","format":"uuid"}},"ownerIds":{"type":"array","items":{"type":"string","format":"uuid"}},"externalId":{"type":"string"},"source":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"archived":{"type":"boolean"},"createdById":{"type":"string","format":"uuid"},"unsubscribedFromAllMessaging":{"type":"boolean","description":"Whether the contact has opted out of all outbound messaging. When `true`, the contact is suppressed across every send channel: email sequences (blocked at enrollment and stopped mid-run), automations, AI-agent email sends, and bulk email/LinkedIn composers.\n"},"unsubscribedFromAllMessagingAt":{"type":"string","format":"date-time","description":"When the contact was marked as unsubscribed from all messaging."}}}},"responses":{"Unauthorized":{"description":"Authentication failed or token is invalid/expired","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}},"paths":{"/api/contacts":{"get":{"tags":["Contacts"],"summary":"List contacts","description":"Returns contacts the user has access to. Filter by workspace using the `where` parameter: `{\"workspaceId\": \"<WORKSPACE_UUID>\"}`.\n","operationId":"listContacts","parameters":[{"$ref":"#/components/parameters/fields"},{"$ref":"#/components/parameters/where"},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/offset"},{"$ref":"#/components/parameters/orderBy"}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"total":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}}}
````

## Create a contact

> Create a new contact in a workspace.\
> \
> Note: If companyId is not provided but email or linkedin is, the system will automatically find or create the associated company.<br>

```json
{"openapi":"3.0.3","info":{"title":"Zero API","version":"1.12.0"},"tags":[{"name":"Contacts","description":"Manage contacts within workspaces"}],"servers":[{"url":"https://api.zero.inc","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"All API requests require a Bearer token in the Authorization header. Create an API key from [Workspace Settings → API keys](https://app.zero.inc/settings/workspace/api)"}},"schemas":{"ContactCreate":{"type":"object","required":["workspaceId"],"properties":{"workspaceId":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid","description":"If not provided but email or linkedin is given, the system will automatically find or create the associated company."},"name":{"type":"string"},"email":{"type":"string","format":"email"},"title":{"type":"string"},"phone":{"type":"string"},"linkedin":{"type":"string"},"x":{"type":"string","description":"X (formerly Twitter) handle"},"facebook":{"type":"string"},"github":{"type":"string"},"avatar":{"type":"string","format":"uri"},"location":{"type":"object","description":"Geographic location as a structured object. Do not pass a plain string.","properties":{"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"continent":{"type":"string"}}},"type":{"type":"string"},"custom":{"type":"object"},"listIds":{"type":"array","items":{"type":"string","format":"uuid"}},"ownerIds":{"type":"array","items":{"type":"string","format":"uuid"}},"externalId":{"type":"string"},"source":{"type":"string"},"unsubscribedFromAllMessaging":{"type":"boolean","description":"Create the contact already opted out of all outbound messaging (for example when importing a suppression list). Defaults to `false`. When set to `true`, also set `unsubscribedFromAllMessagingAt` — it is not stamped automatically.\n"},"unsubscribedFromAllMessagingAt":{"type":"string","format":"date-time","nullable":true,"description":"When the contact opted out. Not set automatically — provide it when creating the contact with `unsubscribedFromAllMessaging` set to true."}}},"Contact":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"workspaceId":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"title":{"type":"string"},"phone":{"type":"string"},"linkedin":{"type":"string"},"x":{"type":"string","description":"X (formerly Twitter) handle"},"facebook":{"type":"string"},"github":{"type":"string"},"avatar":{"type":"string","format":"uri"},"location":{"type":"object","description":"Geographic location of the contact. Stored as a structured object, not a plain string.\n","properties":{"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"continent":{"type":"string"},"countryCode":{"type":"string"},"stateCode":{"type":"string"},"coordinates":{"type":"object","properties":{"lat":{"type":"number"},"lng":{"type":"number"}}}}},"type":{"type":"string"},"custom":{"type":"object","description":"Custom property values, keyed by column UUID. Use `GET /api/columns` to discover available custom property IDs and types. When updating via PATCH, always use dot-notation (e.g. `\"custom.<COLUMN_ID>\": \"value\"`) to avoid overwriting other custom properties.\n"},"listIds":{"type":"array","items":{"type":"string","format":"uuid"}},"ownerIds":{"type":"array","items":{"type":"string","format":"uuid"}},"externalId":{"type":"string"},"source":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"archived":{"type":"boolean"},"createdById":{"type":"string","format":"uuid"},"unsubscribedFromAllMessaging":{"type":"boolean","description":"Whether the contact has opted out of all outbound messaging. When `true`, the contact is suppressed across every send channel: email sequences (blocked at enrollment and stopped mid-run), automations, AI-agent email sends, and bulk email/LinkedIn composers.\n"},"unsubscribedFromAllMessagingAt":{"type":"string","format":"date-time","description":"When the contact was marked as unsubscribed from all messaging."}}}},"responses":{"Unauthorized":{"description":"Authentication failed or token is invalid/expired","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}},"paths":{"/api/contacts":{"post":{"tags":["Contacts"],"summary":"Create a contact","description":"Create a new contact in a workspace.\n\nNote: If companyId is not provided but email or linkedin is, the system will automatically find or create the associated company.\n","operationId":"createContact","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreate"}}}},"responses":{"200":{"description":"Contact created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Contact"},"sideEffects":{"type":"array","items":{}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}}}
```

## Get a contact

> Returns a single contact by ID.

```json
{"openapi":"3.0.3","info":{"title":"Zero API","version":"1.12.0"},"tags":[{"name":"Contacts","description":"Manage contacts within workspaces"}],"servers":[{"url":"https://api.zero.inc","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"All API requests require a Bearer token in the Authorization header. Create an API key from [Workspace Settings → API keys](https://app.zero.inc/settings/workspace/api)"}},"schemas":{"Contact":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"workspaceId":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"title":{"type":"string"},"phone":{"type":"string"},"linkedin":{"type":"string"},"x":{"type":"string","description":"X (formerly Twitter) handle"},"facebook":{"type":"string"},"github":{"type":"string"},"avatar":{"type":"string","format":"uri"},"location":{"type":"object","description":"Geographic location of the contact. Stored as a structured object, not a plain string.\n","properties":{"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"continent":{"type":"string"},"countryCode":{"type":"string"},"stateCode":{"type":"string"},"coordinates":{"type":"object","properties":{"lat":{"type":"number"},"lng":{"type":"number"}}}}},"type":{"type":"string"},"custom":{"type":"object","description":"Custom property values, keyed by column UUID. Use `GET /api/columns` to discover available custom property IDs and types. When updating via PATCH, always use dot-notation (e.g. `\"custom.<COLUMN_ID>\": \"value\"`) to avoid overwriting other custom properties.\n"},"listIds":{"type":"array","items":{"type":"string","format":"uuid"}},"ownerIds":{"type":"array","items":{"type":"string","format":"uuid"}},"externalId":{"type":"string"},"source":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"archived":{"type":"boolean"},"createdById":{"type":"string","format":"uuid"},"unsubscribedFromAllMessaging":{"type":"boolean","description":"Whether the contact has opted out of all outbound messaging. When `true`, the contact is suppressed across every send channel: email sequences (blocked at enrollment and stopped mid-run), automations, AI-agent email sends, and bulk email/LinkedIn composers.\n"},"unsubscribedFromAllMessagingAt":{"type":"string","format":"date-time","description":"When the contact was marked as unsubscribed from all messaging."}}}},"responses":{"Unauthorized":{"description":"Authentication failed or token is invalid/expired","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}},"paths":{"/api/contacts/{contactId}":{"get":{"tags":["Contacts"],"summary":"Get a contact","description":"Returns a single contact by ID.","operationId":"getContact","parameters":[{"name":"contactId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Successful response. If the contact does not exist, the API returns HTTP 200 with an empty body (`{}`).\n","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Contact"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}}}
```

## Delete a contact

> Delete a contact. Use archive=true for soft delete.

```json
{"openapi":"3.0.3","info":{"title":"Zero API","version":"1.12.0"},"tags":[{"name":"Contacts","description":"Manage contacts within workspaces"}],"servers":[{"url":"https://api.zero.inc","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"All API requests require a Bearer token in the Authorization header. Create an API key from [Workspace Settings → API keys](https://app.zero.inc/settings/workspace/api)"}},"schemas":{"Contact":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"workspaceId":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"title":{"type":"string"},"phone":{"type":"string"},"linkedin":{"type":"string"},"x":{"type":"string","description":"X (formerly Twitter) handle"},"facebook":{"type":"string"},"github":{"type":"string"},"avatar":{"type":"string","format":"uri"},"location":{"type":"object","description":"Geographic location of the contact. Stored as a structured object, not a plain string.\n","properties":{"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"continent":{"type":"string"},"countryCode":{"type":"string"},"stateCode":{"type":"string"},"coordinates":{"type":"object","properties":{"lat":{"type":"number"},"lng":{"type":"number"}}}}},"type":{"type":"string"},"custom":{"type":"object","description":"Custom property values, keyed by column UUID. Use `GET /api/columns` to discover available custom property IDs and types. When updating via PATCH, always use dot-notation (e.g. `\"custom.<COLUMN_ID>\": \"value\"`) to avoid overwriting other custom properties.\n"},"listIds":{"type":"array","items":{"type":"string","format":"uuid"}},"ownerIds":{"type":"array","items":{"type":"string","format":"uuid"}},"externalId":{"type":"string"},"source":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"archived":{"type":"boolean"},"createdById":{"type":"string","format":"uuid"},"unsubscribedFromAllMessaging":{"type":"boolean","description":"Whether the contact has opted out of all outbound messaging. When `true`, the contact is suppressed across every send channel: email sequences (blocked at enrollment and stopped mid-run), automations, AI-agent email sends, and bulk email/LinkedIn composers.\n"},"unsubscribedFromAllMessagingAt":{"type":"string","format":"date-time","description":"When the contact was marked as unsubscribed from all messaging."}}}},"responses":{"Unauthorized":{"description":"Authentication failed or token is invalid/expired","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"NotFound":{"description":"The requested resource was not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}},"paths":{"/api/contacts/{contactId}":{"delete":{"tags":["Contacts"],"summary":"Delete a contact","description":"Delete a contact. Use archive=true for soft delete.","operationId":"deleteContact","parameters":[{"name":"contactId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"archive","in":"query","description":"If true, soft deletes (archives) the contact instead of permanently deleting it.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Contact deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"description":"Hard delete (`archive` omitted or `false`): returns `1` on success.\nSoft delete (`archive=true`): returns a single-element array containing the archived contact object.\n","oneOf":[{"type":"integer"},{"type":"array","items":{"$ref":"#/components/schemas/Contact"}}]},"sideEffects":{"type":"array","items":{}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}}}}
```

## Update a contact

> Update an existing contact.\
> \
> \### Updating custom properties\
> \
> \> \*\*⚠️ Important:\*\* Do \*\*not\*\* pass the entire \`custom\` object when updating custom properties — this will overwrite all existing custom property values on the record.\
> \
> Instead, use \*\*dot-notation\*\* to update individual custom properties:\
> \
> \`\`\`json\
> // ✅ Correct — only updates the specific custom property\
> {"custom.54e1ca7d-69c3-4b77-8266-8085b5834116": "hello world"}\
> \
> // ❌ Wrong — overwrites the entire custom object, deleting all other values\
> {"custom": {"54e1ca7d-69c3-4b77-8266-8085b5834116": "hello world"}}\
> \`\`\`\
> \
> Use \`GET /api/columns\` to discover the available custom property IDs and types for the workspace.<br>

````json
{"openapi":"3.0.3","info":{"title":"Zero API","version":"1.12.0"},"tags":[{"name":"Contacts","description":"Manage contacts within workspaces"}],"servers":[{"url":"https://api.zero.inc","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"All API requests require a Bearer token in the Authorization header. Create an API key from [Workspace Settings → API keys](https://app.zero.inc/settings/workspace/api)"}},"schemas":{"ContactUpdate":{"type":"object","properties":{"companyId":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"title":{"type":"string"},"phone":{"type":"string"},"linkedin":{"type":"string"},"x":{"type":"string","description":"X (formerly Twitter) handle"},"facebook":{"type":"string"},"github":{"type":"string"},"avatar":{"type":"string","format":"uri"},"location":{"type":"object","description":"Geographic location as a structured object. Do not pass a plain string.","properties":{"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"continent":{"type":"string"}}},"type":{"type":"string"},"custom":{"type":"object"},"listIds":{"type":"array","items":{"type":"string","format":"uuid"}},"ownerIds":{"type":"array","items":{"type":"string","format":"uuid"}},"externalId":{"type":"string"},"source":{"type":"string"},"unsubscribedFromAllMessaging":{"type":"boolean","description":"Set to `true` to opt the contact out of all outbound messaging. This is the supported way to unsubscribe a contact via the API — once set, the contact is suppressed everywhere: email sequences (new enrollment is blocked and any active run is stopped on its next step), automations, AI-agent email sends, and bulk email/LinkedIn composers. The server does **not** stamp the time automatically, so set `unsubscribedFromAllMessagingAt` in the same request to record when it happened (if omitted, the timestamp is left unchanged). To re-subscribe a contact, `PATCH` this back to `false` — and send `unsubscribedFromAllMessagingAt: null` to clear the record.\n"},"unsubscribedFromAllMessagingAt":{"type":"string","format":"date-time","nullable":true,"description":"Timestamp recording when the contact opted out. Not set automatically — provide it when setting `unsubscribedFromAllMessaging` to true, or send null to clear it when re-subscribing."}}}},"responses":{"Unauthorized":{"description":"Authentication failed or token is invalid/expired","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}},"paths":{"/api/contacts/{contactId}":{"patch":{"tags":["Contacts"],"summary":"Update a contact","description":"Update an existing contact.\n\n### Updating custom properties\n\n> **⚠️ Important:** Do **not** pass the entire `custom` object when updating custom properties — this will overwrite all existing custom property values on the record.\n\nInstead, use **dot-notation** to update individual custom properties:\n\n```json\n// ✅ Correct — only updates the specific custom property\n{\"custom.54e1ca7d-69c3-4b77-8266-8085b5834116\": \"hello world\"}\n\n// ❌ Wrong — overwrites the entire custom object, deleting all other values\n{\"custom\": {\"54e1ca7d-69c3-4b77-8266-8085b5834116\": \"hello world\"}}\n```\n\nUse `GET /api/columns` to discover the available custom property IDs and types for the workspace.\n","operationId":"updateContact","parameters":[{"name":"contactId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpdate"}}}},"responses":{"200":{"description":"Only the changed fields are returned in `data` (e.g. `name`, `updatedAt`, `updatedById`). Use `GET` on the record to retrieve the full updated object.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","description":"Only the changed fields, not the full object."},"sideEffects":{"type":"array","items":{}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}}}
````


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.zero.inc/features/api/contacts.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
