Contacts
The people you email: create, update in batches, search, read their history, export or erase them.
List contacts
GET/projects/{project_id}/contacts
Newest first. Filter by one of search (matches email and name), tag or email at a time.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| project_id | path | string | Required |
| search | query | string | null | Optional |
| tag | query | string | null | Optional |
| query | string | null | Optional | |
| cursor | query | string | null | Optional |
| limit | query | integer | Optional, default 50 |
curl "https://sendocado.com/api/v1/projects/$PROJECT_ID/contacts?tag=trial&limit=50" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"{
"contacts": [
{
"id": "j8ht9lgmxg9edn581u33xtplpft75v2s",
"project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
"email": "maya@example.com",
"first_name": "Maya",
"last_name": "Patel",
"timezone": "Europe/London",
"tags": ["trial"],
"attributes": {
"plan": "trial",
"company": "Northwind"
},
"is_unsubscribed": false,
"is_blocked": false,
"created_at": 1790000000000
}
],
"next_cursor": null
}Errors
| Status | Type | Example message |
|---|---|---|
| 400 | invalid_data | limit: Too big: expected number to be <=200 |
| 401 | unauthorized | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | project_not_found | Project not found. |
| 429 | rate_limited | Too many requests. Try again in 12 seconds. |
Create a contact
POST/projects/{project_id}/contacts
Fails with 409 contact_exists when the address is already in the project; to create or update in one call, use the batch route. A new contact joins every live campaign whose audience it matches.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| project_id | path | string | Required |
Body
Send JSON with every field; use null for a value you do not set.
| Field | Type | Notes |
|---|---|---|
| string | Required | |
| first_name | string | null | Required |
| last_name | string | null | Required |
| timezone | string | null | Required |
| tags | string[] | Required |
| attributes | object of string | Required |
curl -X POST "https://sendocado.com/api/v1/projects/$PROJECT_ID/contacts" \
-H "Authorization: Bearer $SENDOCADO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "maya@example.com",
"first_name": "Maya",
"last_name": "Patel",
"timezone": "Europe/London",
"tags": ["trial"],
"attributes": {
"plan": "trial",
"company": "Northwind"
}
}'{
"id": "j8ht9lgmxg9edn581u33xtplpft75v2s",
"project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
"email": "maya@example.com",
"first_name": "Maya",
"last_name": "Patel",
"timezone": "Europe/London",
"tags": ["trial"],
"attributes": {
"plan": "trial",
"company": "Northwind"
},
"is_unsubscribed": false,
"is_blocked": false,
"created_at": 1790000000000
}Errors
| Status | Type | Example message |
|---|---|---|
| 400 | invalid_data | Send a JSON request body. |
| 401 | unauthorized | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 402 | plan_limit_reached | Your plan allows 5,000 contacts. Upgrade the plan to add more. |
| 404 | project_not_found | Project not found. |
| 409 | contact_exists | maya@example.com is already a contact in this project. |
| 429 | rate_limited | Too many requests. Try again in 12 seconds. |
Create or update contacts in a batch
PUT/projects/{project_id}/contacts/batch
Up to 1,000 contacts, all or nothing: one bad contact refuses the batch and the error names its index. For an existing contact a null field keeps what is stored, tags are added and attributes are set.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| project_id | path | string | Required |
Body
Send JSON with every field; use null for a value you do not set.
| Field | Type | Notes |
|---|---|---|
| contacts | object[] | Required |
| contacts[].email | string | Required |
| contacts[].first_name | string | null | Required |
| contacts[].last_name | string | null | Required |
| contacts[].timezone | string | null | Required |
| contacts[].tags | string[] | Required |
| contacts[].attributes | object of string | Required |
curl -X PUT "https://sendocado.com/api/v1/projects/$PROJECT_ID/contacts/batch" \
-H "Authorization: Bearer $SENDOCADO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{
"email": "maya@example.com",
"first_name": "Maya",
"last_name": null,
"timezone": null,
"tags": ["customer"],
"attributes": {
"plan": "pro"
}
},
{
"email": "leo@example.com",
"first_name": "Leo",
"last_name": "Martin",
"timezone": "Europe/Paris",
"tags": ["trial"],
"attributes": {}
}
]
}'Answers 204 with no body.
Errors
| Status | Type | Example message |
|---|---|---|
| 400 | invalid_data | Send a JSON request body. |
| 401 | unauthorized | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 402 | plan_limit_reached | Your plan allows 5,000 contacts. Upgrade the plan to add more. |
| 404 | project_not_found | Project not found. |
| 429 | rate_limited | Too many requests. Try again in 12 seconds. |
Get a contact
GET/contacts/{contact_id}
is_unsubscribed and is_blocked are read from the address’s history, so they stay true after a contact is deleted and added again.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| contact_id | path | string | Required |
curl "https://sendocado.com/api/v1/contacts/$CONTACT_ID" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"{
"id": "j8ht9lgmxg9edn581u33xtplpft75v2s",
"project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
"email": "maya@example.com",
"first_name": "Maya",
"last_name": "Patel",
"timezone": "Europe/London",
"tags": ["trial"],
"attributes": {
"plan": "trial",
"company": "Northwind"
},
"is_unsubscribed": false,
"is_blocked": false,
"created_at": 1790000000000
}Errors
| Status | Type | Example message |
|---|---|---|
| 401 | unauthorized | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | contact_not_found | Contact not found. |
| 429 | rate_limited | Too many requests. Try again in 12 seconds. |
Update a contact
PUT/contacts/{contact_id}
The email never changes. Tags and attributes become exactly the sets you send. Unsubscribing and blocking have their own routes.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| contact_id | path | string | Required |
Body
Send JSON with every field; use null for a value you do not set.
| Field | Type | Notes |
|---|---|---|
| first_name | string | null | Required |
| last_name | string | null | Required |
| timezone | string | null | Required |
| tags | string[] | Required |
| attributes | object of string | Required |
curl -X PUT "https://sendocado.com/api/v1/contacts/$CONTACT_ID" \
-H "Authorization: Bearer $SENDOCADO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Maya",
"last_name": "Patel",
"timezone": "Europe/London",
"tags": ["trial"],
"attributes": {
"plan": "trial",
"company": "Northwind"
}
}'{
"id": "j8ht9lgmxg9edn581u33xtplpft75v2s",
"project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
"email": "maya@example.com",
"first_name": "Maya",
"last_name": "Patel",
"timezone": "Europe/London",
"tags": ["trial"],
"attributes": {
"plan": "trial",
"company": "Northwind"
},
"is_unsubscribed": false,
"is_blocked": false,
"created_at": 1790000000000
}Errors
| Status | Type | Example message |
|---|---|---|
| 400 | invalid_data | Send a JSON request body. |
| 401 | unauthorized | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | contact_not_found | Contact not found. |
| 429 | rate_limited | Too many requests. Try again in 12 seconds. |
Delete a contact
DELETE/contacts/{contact_id}
Its campaigns end. The address stays unsubscribed or blocked; to remove every trace of it, create a contact erasure.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| contact_id | path | string | Required |
curl -X DELETE "https://sendocado.com/api/v1/contacts/$CONTACT_ID" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"Answers 204 with no body.
Errors
| Status | Type | Example message |
|---|---|---|
| 401 | unauthorized | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | contact_not_found | Contact not found. |
| 429 | rate_limited | Too many requests. Try again in 12 seconds. |
List a contact’s emails and events
GET/contacts/{contact_id}/activity
Every email sent to the contact’s address, newest first, including ones sent before the contact existed. Each lists up to 15 events of each type.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| contact_id | path | string | Required |
| cursor | query | string | null | Optional |
| limit | query | integer | Optional, default 50 |
curl "https://sendocado.com/api/v1/contacts/$CONTACT_ID/activity" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"{
"activity": [
{
"send": {
"id": "thjxjqi3ogz5kok16zv0mwufxbv932by",
"project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
"contact_id": "j8ht9lgmxg9edn581u33xtplpft75v2s",
"to": "maya@example.com",
"subject": "Welcome to Acme, Maya",
"source": {
"type": "campaign_step",
"campaign_id": "h5dnsipzz5fk2z9ri19r0wyojfljooa5",
"step_id": "j8gxbenyjqwx4hh5344tfjgvq4k7bn7x",
"enrollment_id": "j3j4wj99ibag7i1mnbqns6puq80idw37"
},
"status": "delivered",
"has_opened": true,
"has_clicked": true,
"has_replied": false,
"accepted_at": 1790000120000,
"failed_at": null,
"ext_provider_message_id": "4ef9a1c2-7d3b-4f0e-9a51-2c8e6b1d0f73",
"created_at": 1790000120000
},
"events": [
{
"type": "delivered",
"url": null,
"detail": null,
"created_at": 1790000180000
},
{
"type": "opened",
"url": null,
"detail": null,
"created_at": 1790005700000
},
{
"type": "clicked",
"url": "https://acme.example/setup",
"detail": null,
"created_at": 1790005760000
}
]
}
],
"next_cursor": null
}Errors
| Status | Type | Example message |
|---|---|---|
| 400 | invalid_data | limit: Too big: expected number to be <=200 |
| 401 | unauthorized | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | contact_not_found | Contact not found. |
| 429 | rate_limited | Too many requests. Try again in 12 seconds. |
List a contact’s campaigns
GET/contacts/{contact_id}/enrollments
Every campaign the contact is in or has left, with why they left.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| contact_id | path | string | Required |
curl "https://sendocado.com/api/v1/contacts/$CONTACT_ID/enrollments" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"{
"enrollments": [
{
"id": "j3j4wj99ibag7i1mnbqns6puq80idw37",
"campaign_id": "h5dnsipzz5fk2z9ri19r0wyojfljooa5",
"contact": {
"id": "j8ht9lgmxg9edn581u33xtplpft75v2s",
"email": "maya@example.com",
"first_name": "Maya",
"last_name": "Patel"
},
"status": "active",
"exit_reason": null,
"next_action": {
"type": "check_branch",
"step_id": "j8gxbenyjqwx4hh5344tfjgvq4k7bn7x",
"due_at": 1790259200000
},
"created_at": 1790000060000,
"exited_at": null
}
],
"next_cursor": null
}Errors
| Status | Type | Example message |
|---|---|---|
| 401 | unauthorized | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | contact_not_found | Contact not found. |
| 429 | rate_limited | Too many requests. Try again in 12 seconds. |
Export everything about a contact
GET/contacts/{contact_id}/export
A subject-access export: every fact held about the contact and its address, in one JSON document.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| contact_id | path | string | Required |
curl "https://sendocado.com/api/v1/contacts/$CONTACT_ID/export" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"{
"contact": {
"id": "j8ht9lgmxg9edn581u33xtplpft75v2s",
"project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
"email": "maya@example.com",
"first_name": "Maya",
"last_name": "Patel",
"timezone": "Europe/London",
"tags": ["trial"],
"attributes": {
"plan": "trial",
"company": "Northwind"
},
"is_unsubscribed": false,
"is_blocked": false,
"created_at": 1790000000000
},
"enrollments": [
{
"id": "j3j4wj99ibag7i1mnbqns6puq80idw37",
"campaign_id": "h5dnsipzz5fk2z9ri19r0wyojfljooa5",
"exits": [],
"created_at": 1790000060000
}
],
"sends": [
{
"send": {
"id": "thjxjqi3ogz5kok16zv0mwufxbv932by",
"to": "maya@example.com",
"subject": "Welcome to Acme, Maya",
"source": {
"type": "campaign_step",
"campaign_id": "h5dnsipzz5fk2z9ri19r0wyojfljooa5",
"step_id": "j8gxbenyjqwx4hh5344tfjgvq4k7bn7x",
"enrollment_id": "j3j4wj99ibag7i1mnbqns6puq80idw37"
},
"status": "delivered",
"accepted_at": 1790000120000,
"failed_at": null,
"created_at": 1790000120000
},
"events": [
{
"type": "delivered",
"url": null,
"detail": null,
"created_at": 1790000180000
},
{
"type": "opened",
"url": null,
"detail": null,
"created_at": 1790005700000
},
{
"type": "clicked",
"url": "https://acme.example/setup",
"detail": null,
"created_at": 1790005760000
}
]
}
],
"replies": [
{
"id": "kdfy6spsc3lkr2aqxv9upctnwlavyf4r",
"send_id": "thjxjqi3ogz5kok16zv0mwufxbv932by",
"from_email": "maya@example.com",
"from_name": "Maya Patel",
"subject": "Re: Welcome to Acme, Maya",
"text": "Thanks! Quick question: can I import from a CSV?",
"created_at": 1790010800000
}
],
"unsubscribes": [],
"resubscribes": [],
"bounces": [],
"complaints": [],
"manual_blocks": [],
"unblocks": [],
"consents": [],
"transactional_emails": [],
"test_emails": []
}Errors
| Status | Type | Example message |
|---|---|---|
| 401 | unauthorized | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | contact_not_found | Contact not found. |
| 429 | rate_limited | Too many requests. Try again in 12 seconds. |
Erase an address
POST/projects/{project_id}/contact-erasures
Deletes the contact and everything sent to the address, in the background. Its unsubscribes and blocks are kept without the address, so it stays unmailable. Read the erasure until it is completed.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| project_id | path | string | Required |
Body
Send JSON with every field; use null for a value you do not set.
| Field | Type | Notes |
|---|---|---|
| string | Required |
curl -X POST "https://sendocado.com/api/v1/projects/$PROJECT_ID/contact-erasures" \
-H "Authorization: Bearer $SENDOCADO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "maya@example.com"
}'{
"id": "ez732pgojj7g3f9caioctiq71hget7my",
"status": "pending",
"created_at": 1790000000000,
"completed_at": null
}Errors
| Status | Type | Example message |
|---|---|---|
| 400 | invalid_data | Send a JSON request body. |
| 401 | unauthorized | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | project_not_found | Project not found. |
| 429 | rate_limited | Too many requests. Try again in 12 seconds. |
Get an erasure
GET/contact-erasures/{contact_erasure_id}
status becomes completed once every record is gone.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| contact_erasure_id | path | string | Required |
curl "https://sendocado.com/api/v1/contact-erasures/$CONTACT_ERASURE_ID" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"{
"id": "ez732pgojj7g3f9caioctiq71hget7my",
"status": "completed",
"created_at": 1790000000000,
"completed_at": 1790000120000
}Errors
| Status | Type | Example message |
|---|---|---|
| 401 | unauthorized | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | contact_erasure_not_found | Contact erasure not found. |
| 429 | rate_limited | Too many requests. Try again in 12 seconds. |
List attribute keys in use
GET/projects/{project_id}/contact-attribute-keys
Every distinct attribute key on the project’s contacts. is_complete is false when there are more than one read lists.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| project_id | path | string | Required |
curl "https://sendocado.com/api/v1/projects/$PROJECT_ID/contact-attribute-keys" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"{
"keys": ["company", "plan"],
"is_complete": true
}Errors
| Status | Type | Example message |
|---|---|---|
| 401 | unauthorized | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | project_not_found | Project not found. |
| 429 | rate_limited | Too many requests. Try again in 12 seconds. |