Contacts
People you sell to. A contact can be linked to several accounts, and to leads, opportunities and activities.
- 9 operations
- 60 fields
- Schema Contact
- GraphQL: contact
- ContactDataExRelations
- ContactKPIs
- ContactSharingClientRelations
- ContactSharingSalesUnitRelations
A contact's links to accounts are ContactAccountRelations. Duplicate contacts can be merged into one.
What the API requires
A create needs first_name or last_name as well as owner_id, although the spec marks only owner_id as required. Without either name the API answers HTTP 422 with code 119, At least one of the fields [first_name, last_name] is required. (docs sandbox, 2026-10-14).
| Method | Path | Operation |
|---|---|---|
| GET | /entities/Contacts | List contacts |
| POST | /entities/Contacts | Create a contact |
| GET | /entities/Contacts/{id} | Get a contact |
| PATCH | /entities/Contacts/{id} | Update a contact |
| DEL | /entities/Contacts/{id} | Delete a contact |
| POST | /entities/Contacts/batch-modify | Create or update contacts in a batch |
| POST | /entities/Contacts/batch-delete | Delete contacts in a batch |
| POST | /entities/Contacts/batch-restore | Restore deleted contacts |
| POST | /entities/Contacts/merge | Merge contacts |
GET List contacts
Returns the contacts the API application can read, one page at a time. In the list envelope, total counts every match and page_info pages through them.
Query parameters
| Name | Type | Description |
|---|---|---|
include-deleted | boolean | Include soft-deleted records. Lists only; get-by-id refuses it. |
first | integer | Page size. Default 30, maximum 100; a larger value is refused with HTTP 400, code 117. |
last | integer | Return the last N records of the result; together with first, the last N of the first M. Maximum 100. |
before | string | Cursor from page_info.start_cursor, to page backwards. URL-encode it. |
after | string | Cursor from page_info.end_cursor of the previous page. URL-encode it: cursors are base64, and a raw + becomes a space. |
expand | string | Related records to return inline instead of their URIs, comma-separated; dots reach nested relations, as in account_relations.account. |
order-by | string | Fields to sort by, comma-separated. A leading - sorts descending, as in -modified; dotted paths sort by related fields. |
filter | object | filter[field]=value. Separate several values with a raw comma (OR), as in filter[name]=north,south. Encode a comma that belongs to a value as %2C. URLSearchParams and Python requests encode every comma, so build the filter part of the query string yourself. Dotted paths filter on related records, as in filter[owner.email]. Encode a + in a value as %2B: query values are form-decoded, so a raw + becomes a space. |
filter-op | object | filter-op[field]=operator, one per field: eq (the default), ieq, not-eq, not-ieq, contains, icontains, starts, istarts, ends, iends, gt, gte, lt, lte, between, null, not-null, empty and not-empty. null, not-null, empty and not-empty take no filter value; gt, gte, lt and lte take exactly one. |
load-only | string | Fields to return, comma-separated (raw or encoded commas both work here); dots select nested fields, as in owner.id. All fields are returned without it. Lists only: get-by-id refuses it with HTTP 400, code 118. |
Responses
200 Page of Contact records.: success, total, page_info, data 400 Bad request 401 Not authenticated 429 Rate limited 500 Server error
curl 'https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts?first=10' \
--user "$COEVERA_API_KEY:$COEVERA_API_SECRET"fetch('https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts?first=10', {
headers: {
Authorization: "Basic " + btoa(`${process.env.COEVERA_API_KEY}:${process.env.COEVERA_API_SECRET}`)
}
})import os
import requests
response = requests.get(
"https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts",
params={
"first": "10"
},
auth=(os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"])
)POST Create a contact
Creates one contact and answers 201 with the record. owner_id and a first_name or last_name are required; the spec marks only owner_id.
Query parameters
| Name | Type | Description |
|---|---|---|
validation-level | integer | Bit mask that relaxes validation on this write. 0 (the default) validates everything. Bits: 2 skip unchanged fields, 4 skip admin-defined validations, 8 allow read-only fields, 16 skip relation checks, 32 skip value checks, 64 skip invalid recalculations, 128 skip step-checklist gates, 256 add conditional field requirements, 512 skip uniqueness checks. Add bits together (20 = 4 + 16); member names and comma lists such as 4,16 work too. A value with an internal-only bit is refused with HTTP 400, code 117. |
Request body
The new record's fields. Relations are set by id. Schema createContactInput; the fields are in Contact fields. Required: owner_id.
Responses
201 The created Contact record.: success, data 400 Bad request 401 Not authenticated 422 Validation failed 429 Rate limited 500 Server error
curl 'https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts' \
--request POST \
--user "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
--header 'Content-Type: application/json' \
--data '{
"first_name": "Maria",
"last_name": "Anders",
"owner_id": "37000000-0000-4000-a000-000000000001",
"email1": "maria.anders@example.com",
"position": "Head of Operations"
}'fetch('https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts', {
method: 'POST',
headers: {
Authorization: "Basic " + btoa(`${process.env.COEVERA_API_KEY}:${process.env.COEVERA_API_SECRET}`),
'Content-Type': 'application/json'
},
body: JSON.stringify({
first_name: 'Maria',
last_name: 'Anders',
owner_id: '37000000-0000-4000-a000-000000000001',
email1: 'maria.anders@example.com',
position: 'Head of Operations'
})
})import os
import requests
response = requests.post(
"https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts",
headers={
"Content-Type": "application/json"
},
auth=(os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"]),
json={
"first_name": "Maria",
"last_name": "Anders",
"owner_id": "37000000-0000-4000-a000-000000000001",
"email1": "maria.anders@example.com",
"position": "Head of Operations"
}
)GET Get a contact
Returns one contact by id. expand returns related records inline instead of their URIs.
Path parameters
| Name | Type | Description |
|---|---|---|
id | string (uuid) | Id of the Contact record to retrieve. Required |
Query parameters
| Name | Type | Description |
|---|---|---|
expand | string | Related records to return inline instead of their URIs, comma-separated; dots reach nested relations. The only parameter this call accepts. |
Responses
200 The Contact record.: success, data 400 Bad request 401 Not authenticated 404 Not found 429 Rate limited 500 Server error
curl 'https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/10000000-0000-4000-a000-000000000001?expand=owner' \
--user "$COEVERA_API_KEY:$COEVERA_API_SECRET"fetch('https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/10000000-0000-4000-a000-000000000001?expand=owner', {
headers: {
Authorization: "Basic " + btoa(`${process.env.COEVERA_API_KEY}:${process.env.COEVERA_API_SECRET}`)
}
})import os
import requests
response = requests.get(
"https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/10000000-0000-4000-a000-000000000001",
params={
"expand": "owner"
},
auth=(os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"])
)PATCH Update a contact
Changes the fields you send and leaves the rest as they are; there is no PUT.
Path parameters
| Name | Type | Description |
|---|---|---|
id | string (uuid) | Id of the Contact record to update. Required |
Query parameters
| Name | Type | Description |
|---|---|---|
validation-level | integer | Bit mask that relaxes validation on this write. 0 (the default) validates everything. Bits: 2 skip unchanged fields, 4 skip admin-defined validations, 8 allow read-only fields, 16 skip relation checks, 32 skip value checks, 64 skip invalid recalculations, 128 skip step-checklist gates, 256 add conditional field requirements, 512 skip uniqueness checks. Add bits together (20 = 4 + 16); member names and comma lists such as 4,16 work too. A value with an internal-only bit is refused with HTTP 400, code 117. |
Request body
The fields to change. Schema updateContactInput; the fields are in Contact fields.
Responses
200 The changed Contact record.: success, data 400 Bad request 401 Not authenticated 404 Not found 422 Validation failed 429 Rate limited 500 Server error
curl 'https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/10000000-0000-4000-a000-000000000001' \
--request PATCH \
--user "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
--header 'Content-Type: application/json' \
--data '{
"phone1": "+1 555 0199"
}'fetch('https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/10000000-0000-4000-a000-000000000001', {
method: 'PATCH',
headers: {
Authorization: "Basic " + btoa(`${process.env.COEVERA_API_KEY}:${process.env.COEVERA_API_SECRET}`),
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone1: '+1 555 0199'
})
})import os
import requests
response = requests.patch(
"https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/10000000-0000-4000-a000-000000000001",
headers={
"Content-Type": "application/json"
},
auth=(os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"]),
json={
"phone1": "+1 555 0199"
}
)DEL Delete a contact
Soft-deletes one contact and answers with its id. Its account relations stay active.
Path parameters
| Name | Type | Description |
|---|---|---|
id | string (uuid) | Id of the Contact record to delete. Required |
Responses
200 Deletion confirmation.: success, data 400 Bad request 401 Not authenticated 404 Not found 429 Rate limited 500 Server error
curl 'https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/10000000-0000-4000-a000-000000000001' \
--request DELETE \
--user "$COEVERA_API_KEY:$COEVERA_API_SECRET"fetch('https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/10000000-0000-4000-a000-000000000001', {
method: 'DELETE',
headers: {
Authorization: "Basic " + btoa(`${process.env.COEVERA_API_KEY}:${process.env.COEVERA_API_SECRET}`)
}
})import os
import requests
response = requests.delete(
"https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/10000000-0000-4000-a000-000000000001",
auth=(os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"])
)POST Create or update contacts in a batch
An item with an id updates that contact; an item without one creates a contact. By default one invalid item fails the whole call with HTTP 422; with rollback-method=1 the response has an id or an error for each item, in input order.
Query parameters
| Name | Type | Description |
|---|---|---|
validation-level | integer | Bit mask that relaxes validation on this write. 0 (the default) validates everything. Bits: 2 skip unchanged fields, 4 skip admin-defined validations, 8 allow read-only fields, 16 skip relation checks, 32 skip value checks, 64 skip invalid recalculations, 128 skip step-checklist gates, 256 add conditional field requirements, 512 skip uniqueness checks. Add bits together (20 = 4 + 16); member names and comma lists such as 4,16 work too. A value with an internal-only bit is refused with HTTP 400, code 117. |
rollback-method | integer | 0, the default: one failed item fails the whole batch (HTTP 422, or 404 for an unknown id) and nothing is written. 1: the valid items are written and the call answers 200 with success false and one result per item, an id or an error, in input order. Check every item. |
Request body
A JSON array of records: an item with an id updates, an item without one creates. Schema batchContactInput; the fields are in Contact fields. Required: owner_id.
Responses
200 Result per item, in request order: the id of the created or updated record, or the error.: success, data 400 Bad request 401 Not authenticated 422 Validation failed 429 Rate limited 500 Server error
curl 'https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/batch-modify' \
--request POST \
--user "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
--header 'Content-Type: application/json' \
--data '[
{
"owner_id": "37000000-0000-4000-a000-000000000001"
},
{
"id": "10000000-0000-4000-a000-000000000001",
"phone1": "+1 555 0199"
}
]'fetch('https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/batch-modify', {
method: 'POST',
headers: {
Authorization: "Basic " + btoa(`${process.env.COEVERA_API_KEY}:${process.env.COEVERA_API_SECRET}`),
'Content-Type': 'application/json'
},
body: JSON.stringify([
{
owner_id: '37000000-0000-4000-a000-000000000001'
},
{
id: '10000000-0000-4000-a000-000000000001',
phone1: '+1 555 0199'
}
])
})import os
import requests
response = requests.post(
"https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/batch-modify",
headers={
"Content-Type": "application/json"
},
auth=(os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"]),
json=[
{
"owner_id": "37000000-0000-4000-a000-000000000001"
},
{
"id": "10000000-0000-4000-a000-000000000001",
"phone1": "+1 555 0199"
}
]
)POST Delete contacts in a batch
Soft-deletes the contacts whose ids are in the body, a JSON array.
Query parameters
| Name | Type | Description |
|---|---|---|
rollback-method | integer | 0, the default: one failed item fails the whole batch (HTTP 422, or 404 for an unknown id) and nothing is written. 1: the valid items are written and the call answers 200 with success false and one result per item, an id or an error, in input order. Check every item. |
Request body
A JSON array of ids.
Responses
200 Result per id, in request order: the id of the deleted record, or the error.: success, data 400 Bad request 401 Not authenticated 404 Not found 422 Validation failed 429 Rate limited 500 Server error
curl 'https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/batch-delete' \
--request POST \
--user "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
--header 'Content-Type: application/json' \
--data '[
"10000000-0000-4000-a000-000000000001",
"10000000-0000-4000-a000-000000000002"
]'fetch('https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/batch-delete', {
method: 'POST',
headers: {
Authorization: "Basic " + btoa(`${process.env.COEVERA_API_KEY}:${process.env.COEVERA_API_SECRET}`),
'Content-Type': 'application/json'
},
body: JSON.stringify(['10000000-0000-4000-a000-000000000001', '10000000-0000-4000-a000-000000000002'])
})import os
import requests
response = requests.post(
"https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/batch-delete",
headers={
"Content-Type": "application/json"
},
auth=(os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"]),
json=[
"10000000-0000-4000-a000-000000000001",
"10000000-0000-4000-a000-000000000002"
]
)POST Restore deleted contacts
Restores the soft-deleted contacts whose ids are in the body, a JSON array.
Query parameters
| Name | Type | Description |
|---|---|---|
rollback-method | integer | 0, the default: one failed item fails the whole batch (HTTP 422, or 404 for an unknown id) and nothing is written. 1: the valid items are written and the call answers 200 with success false and one result per item, an id or an error, in input order. Check every item. |
Request body
A JSON array of ids.
Responses
200 Result per id, in request order: the id of the restored record, or the error.: success, data 400 Bad request 401 Not authenticated 404 Not found 422 Validation failed 429 Rate limited 500 Server error
curl 'https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/batch-restore' \
--request POST \
--user "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
--header 'Content-Type: application/json' \
--data '[
"10000000-0000-4000-a000-000000000001",
"10000000-0000-4000-a000-000000000002"
]'fetch('https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/batch-restore', {
method: 'POST',
headers: {
Authorization: "Basic " + btoa(`${process.env.COEVERA_API_KEY}:${process.env.COEVERA_API_SECRET}`),
'Content-Type': 'application/json'
},
body: JSON.stringify(['10000000-0000-4000-a000-000000000001', '10000000-0000-4000-a000-000000000002'])
})import os
import requests
response = requests.post(
"https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/batch-restore",
headers={
"Content-Type": "application/json"
},
auth=(os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"]),
json=[
"10000000-0000-4000-a000-000000000001",
"10000000-0000-4000-a000-000000000002"
]
)POST Merge contacts
Merges duplicates into one contact. master is the contact to keep, with any field values to keep; the contacts in secondary_ids are merged into it.
Request body
master, the record to keep with any field values to keep, and secondary_ids, the duplicates to merge into it. Schema batchContactInput; the fields are in Contact fields.
Responses
200 The merged master record.: success, data 400 Bad request 401 Not authenticated 422 Validation failed 429 Rate limited 500 Server error
curl 'https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/merge' \
--request POST \
--user "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
--header 'Content-Type: application/json' \
--data '{
"master": {
"id": "10000000-0000-4000-a000-000000000001"
},
"secondary_ids": [
"10000000-0000-4000-a000-000000000002"
]
}'fetch('https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/merge', {
method: 'POST',
headers: {
Authorization: "Basic " + btoa(`${process.env.COEVERA_API_KEY}:${process.env.COEVERA_API_SECRET}`),
'Content-Type': 'application/json'
},
body: JSON.stringify({
master: {
id: '10000000-0000-4000-a000-000000000001'
},
secondary_ids: ['10000000-0000-4000-a000-000000000002']
})
})import os
import requests
response = requests.post(
"https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}/entities/Contacts/merge",
headers={
"Content-Type": "application/json"
},
auth=(os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"]),
json={
"master": {
"id": "10000000-0000-4000-a000-000000000001"
},
"secondary_ids": [
"10000000-0000-4000-a000-000000000002"
]
}
)Contact fields
| Field | Type | Notes |
|---|---|---|
id | uuid | Read-only Unique identifier of entity. |
owner_id | uuid → Clients | Required on create Id of the contact owner. |
created | date-time | Creation time. |
picture | uri → CloudObjects | Profile picture associated with the contact. |
address | string | Street address of the contact. |
city | string | City of the contact address. |
comments | string | Free-form notes describing the contact. |
contact_type_id | uuid → ContactTypes | Id of the contact type assigned to this contact. |
country | string | Country of the contact address. |
currency_exchange_rates_list_id | uuid → CurrencyExchangeRatesLists | Id of the currency exchange rate list applied to this contact. |
email1 | string | Primary email address of the contact. |
email2 | string | Additional email address. |
email3 | string | Additional email address. |
email4 | string | Additional email address. |
email5 | string | Additional email address. |
Show all 60 fields
| Field | Type | Notes |
|---|---|---|
first_name | string | Given name of the contact. |
gender | integer | Gender of the contact: Unknown when not specified, Male, Female or NonBinary.0 Unknown · 1 Male · 2 Female · 3 NonBinary |
is_archived | boolean | True when the contact has been archived. |
last_name | string | Family name of the contact. |
middle_name | string | Middle name of the contact, if applicable. |
phone1 | string | Primary phone number of the contact. |
phone2 | string | Additional phone number. |
phone3 | string | Additional phone number. |
phone4 | string | Additional phone number. |
phone5 | string | Additional phone number. |
picture_id | uuid → CloudObjects | Id of the profile picture document. |
position | string | Job title or position of the contact. |
quick_account_name | string | Free-text company name used when the contact is not linked to an actual Account record. |
share_mode | integer | Sharing model applied on top of the owner/unit visibility (e.g.0 Standard · 1 Private · 2 AllView · 3 AllEdit |
state_province | string | State or province of the contact address. |
title | string | Honorific or salutation prefix (e.g. |
unit_id | uuid → SalesUnits | Id of the sales unit. |
zip_code | string | ZIP or postal code of the contact address. |
revision | integer | Revision when entity was lastly changed. |
social_media | uri → ContactSocialRelations | Social network profiles linked to this contact (e.g. |
account_relations | array of uri → ContactAccountRelations | Accounts this contact is linked to. |
sharing_units | array of uri → ContactSharingSalesUnitRelations | Sales units that have been granted shared access to this contact. |
sharing_clients | array of uri → ContactSharingClientRelations | Users that have been granted shared access to this contact. |
documents | array of uri → CloudObjectRelations | Documents linked to this contact. |
exchange_entity_id | string | External identifier of the record in the linked third-party service (e.g. |
gapi_entity_id | string | External identifier of the record in the linked third-party service (e.g. |
o365_entity_id | string | External identifier of the record in the linked third-party service (e.g. |
primary_account_account_roles | array of uri → ContactAccountAccountRoleRelations | Roles of Contact in his primary Account. |
account_position | string | Job position of Contact in his primary Account or quick account position if Contact has no primary Account. |
is_unsubscribed | boolean | If account has any unsubscribed email. |
tags | array of uri → TagRelations | Tag labels applied to this contact. |
static_profiles | array of uri → ProfileRelations | Saved view profiles this contact currently matches. |
primary_account_relationship | string | Org chart relationship of Contact in his primary Account. |
is_delete_protected | boolean | Read-only True when the record is protected against deletion because it must always exist in the space. |
is_deleted | boolean | Read-only Specifies if the entity is considered deleted. |
modified | date-time | Read-only Last modification time. |
contact_type | uri → ContactTypes | Read-only Contact type that classifies this contact. |
currency_exchange_rates_list | uri → CurrencyExchangeRatesLists | Read-only Currency exchange rate list used to convert monetary fields linked to this contact. |
owner | uri → Clients | Read-only Sales user who owns this contact. |
unit | uri → SalesUnits | Read-only Sales unit this contact is assigned to. |
formatted_name | string | Read-only Display version of the contact name (title, first, middle, last). |
primary_account | uri → Accounts | Read-only Primary account this contact belongs to. |
is_favorite | boolean | Read-only True when the current authenticated user has marked this contact as favorite. |
primary_account_position | string | Read-only Job position of Contact in his primary Account. |
modified_by_user | date-time | Read-only Timestamp of the most recent modification made by an end user (excluding system changes). |
Custom fields appear in your space's live spec: fetch it with your credentials.
Errors
Every error body has success: false, http_status, code, name and message. Branch on code: the status alone does not tell a missing permission from a bad parameter. All codes: error codes; how to handle them: Errors.
| HTTP | When |
|---|---|
| 400 | Bad request: a bad or unknown parameter (codes 117, 118), a missing permission (code 50), conflicting data (code 2002) or any other API error. The REST API answers 400 where others answer 403 or 409; check code. |
| 401 | Not authenticated: missing or wrong credentials, a credential of another space, or an expired token (code 401, ERROR_UNAUTHORIZED). |
| 404 | No such record or entity (code 143 ERROR_NO_ENTITY, or 2006 ERROR_UNKNOWN_TYPE). |
| 422 | Validation failed (code 40000, with the failing fields) or a required parameter is missing (code 119). By default one failed item fails a whole batch and nothing is written. |
| 429 | Too many requests (code 182). Wait for the seconds in Retry-After, then retry. |
| 500 | Unexpected server error (code 1000, ERROR_UNKNOWN). |
Webhook events for contacts
Contact.Create Contact.Delete Contact.DocumentLinked Contact.OwnerChanged Contact.Update See payloads
Subscribe with Create a webhook.