SonarSend API Endpoints Contacts

Contacts

Contact CRUD, upsert-by-email, search, counts, bulk actions and CSV export. See the Contacts guide.

14 endpoints Base URL · https://api.sonarsend.com Auth · X-API-Key
The Contact object 62 fields
address_line1string · nullable
address_line2string · nullable
anonymized_atdate-time · nullable
bounce_typeenum · nullable
One of: hard, soft, null
category_subscriptionsobject

Category-level opt-out state, keyed by email category ID. Source of truth for categorised email types.

citystring · nullable
companystring · nullable
countrystring · nullable
created_atdate-time
custom_fieldsobject

Values for fields declared via the custom-fields API. Writing an undeclared key returns UNKNOWN_FIELD.

customer_scorenumber
deliverability_reasonstring · nullable
deliverability_statusenum
One of: healthy, watch, at_risk, suppressed
deliverability_updated_atdate-time · nullable
do_not_contactboolean
emailstring

The match key for upserts. Unique per tenant.

email_domainstring · nullable
external_idstring · nullable

Your own record key. Keep it populated so a re-sync can reconcile.

first_namestring · nullable
first_touch_atdate-time · nullable
first_touch_campaignstring · nullable
first_touch_countrystring · nullable
first_touch_landing_urlstring · nullable
first_touch_mediumstring · nullable
first_touch_referrerstring · nullable
first_touch_sourcestring · nullable
first_touch_timezonestring · nullable
global_statusenum

Account-wide sendability. suppressed is set by a soft delete.

One of: active, unsubscribed, bounced, complained, suppressed
iduuid

Canonical contact ID. Store this.

last_activity_atdate-time · nullable
last_email_clicked_atdate-time · nullable
last_email_opened_atdate-time · nullable
last_email_sent_atdate-time · nullable
last_namestring · nullable
last_page_viewed_atdate-time · nullable
last_touch_atdate-time · nullable
last_touch_campaignstring · nullable
last_touch_mediumstring · nullable
last_touch_sourcestring · nullable
member_added_atdate-time · nullable
mx_recordstring · nullable
phonestring · nullable
postal_codestring · nullable
prospect_scorenumber
score_updated_atdate-time · nullable
smtp_providerstring · nullable
sourcestring · nullable

Free-text origin label supplied by the caller.

stage_iduuid · nullable
statestring · nullable
status_before_suppressenum · nullable
One of: active, unsubscribed, bounced, complained, suppressed, null
subscriptionsobject

Per-email-type opt-in state. A missing key means subscribed.

suppressed_atdate-time · nullable
tagsstring[]
tenant_iduuid
timezonestring · nullable
titlestring · nullable
total_emails_clickedinteger
total_emails_openedinteger
total_emails_sentinteger
unsubscribed_atdate-time · nullable
updated_atdate-time
websitestring · nullable
The ContactWithIdentityToken object 63 fields
address_line1string · nullable
address_line2string · nullable
anonymized_atdate-time · nullable
bounce_typeenum · nullable
One of: hard, soft, null
category_subscriptionsobject

Category-level opt-out state, keyed by email category ID. Source of truth for categorised email types.

citystring · nullable
companystring · nullable
countrystring · nullable
created_atdate-time
custom_fieldsobject

Values for fields declared via the custom-fields API. Writing an undeclared key returns UNKNOWN_FIELD.

customer_scorenumber
deliverability_reasonstring · nullable
deliverability_statusenum
One of: healthy, watch, at_risk, suppressed
deliverability_updated_atdate-time · nullable
do_not_contactboolean
emailstring

The match key for upserts. Unique per tenant.

email_domainstring · nullable
external_idstring · nullable

Your own record key. Keep it populated so a re-sync can reconcile.

first_namestring · nullable
first_touch_atdate-time · nullable
first_touch_campaignstring · nullable
first_touch_countrystring · nullable
first_touch_landing_urlstring · nullable
first_touch_mediumstring · nullable
first_touch_referrerstring · nullable
first_touch_sourcestring · nullable
first_touch_timezonestring · nullable
global_statusenum

Account-wide sendability. suppressed is set by a soft delete.

One of: active, unsubscribed, bounced, complained, suppressed
iduuid

Canonical contact ID. Store this.

identity_tokenstring

Present only when the request passed ?include_identity_token=true and identity signing is configured. Hand it to the client-side tracking SDK to link anonymous visits to this contact.

last_activity_atdate-time · nullable
last_email_clicked_atdate-time · nullable
last_email_opened_atdate-time · nullable
last_email_sent_atdate-time · nullable
last_namestring · nullable
last_page_viewed_atdate-time · nullable
last_touch_atdate-time · nullable
last_touch_campaignstring · nullable
last_touch_mediumstring · nullable
last_touch_sourcestring · nullable
member_added_atdate-time · nullable
mx_recordstring · nullable
phonestring · nullable
postal_codestring · nullable
prospect_scorenumber
score_updated_atdate-time · nullable
smtp_providerstring · nullable
sourcestring · nullable

Free-text origin label supplied by the caller.

stage_iduuid · nullable
statestring · nullable
status_before_suppressenum · nullable
One of: active, unsubscribed, bounced, complained, suppressed, null
subscriptionsobject

Per-email-type opt-in state. A missing key means subscribed.

suppressed_atdate-time · nullable
tagsstring[]
tenant_iduuid
timezonestring · nullable
titlestring · nullable
total_emails_clickedinteger
total_emails_openedinteger
total_emails_sentinteger
unsubscribed_atdate-time · nullable
updated_atdate-time
websitestring · nullable

List contacts

GET /t/{slug}/api/contacts/

A cursor-paged list of contacts. Page by passing the previous response's next_cursor back as cursor; has_more is false and next_cursor is null on the last page.

The cursor encodes the sort it was created under, so sort and dir must not change between pages — they do not silently re-sort, the request is rejected.

For a full read-out of the audience, prefer POST /api/contacts/export over paging this endpoint: one export produces one CSV instead of thousands of requests against your rate-limit budget.

cursor string

Opaque next_cursor from the previous page. Omit for the first page.

limit string

Page size. Defaults to 50; clamped to 1–200.

status string

Filter by account-wide status. Soft-deleted (suppressed) contacts are excluded unless you ask for them.

search string

Free-text match across name, email and company.

sort string

Sort column. One of email, first_name, last_name, company, title, phone, city, state, country, postal_code, source, global_status, deliverability_status, do_not_contact, prospect_score, customer_score, total_emails_sent, total_emails_opened, total_emails_clicked, last_activity_at, last_email_sent_at, last_email_opened_at, last_email_clicked_at, last_page_viewed_at, created_at, updated_at, stage_id. An unrecognised value is not an error — it falls back to created_at.

dir string

Sort direction. Defaults to desc.

filters string

JSON-encoded array of { field, operator, value } objects. Malformed JSON returns INVALID_FILTERS.

definition string

JSON-encoded segment definition, for smart filtering by the same rules a dynamic segment uses. Combined with filters as an intersection.

A page of Contact objects, under data.

200

A page of contacts, newest first by default.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/contacts" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "data": [
    {
      "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "email": "jamie@example.com",
      "first_name": "Jamie",
      "last_name": "Reed",
      "title": "Head of Growth"
    }
  ],
  "next_cursor": "eyJpZCI6IjljMjE4OTVkIn0",
  "has_more": true
}

Create a contact

POST /t/{slug}/api/contacts/

Creates a contact. Strictly a create: an existing contact with the same email returns 409 DUPLICATE_EMAIL rather than being updated — use PUT /api/contacts when you want create-or-update.

A key in custom_fields must already be declared through the custom-fields API, or the call fails with UNKNOWN_FIELD. smtp_provider and mx_record are resolved by the platform and are not settable.

include_identity_token string

Return an identity_token for the client-side tracking SDK alongside the contact.

emailstringRequired
address_line1string · nullable
address_line2string · nullable
citystring · nullable
companystring · nullable
countrystring · nullable
custom_fieldsobject
do_not_contactboolean
external_idstring · nullable
first_namestring · nullable
last_namestring · nullable
phonestring · nullable
sourcestring · nullable
stage_idstring
statestring · nullable
subscriptionsobject
tagsstring[]
timezonestring · nullable
titlestring · nullable
websitestring · nullable

The ContactWithIdentityToken object.

201

The contact was created.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/contacts" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jamie@example.com",
    "first_name": "Jamie",
    "last_name": "Reed",
    "title": "Head of Growth",
    "company": "Acme",
    "phone": "+1 555 0100"
  }'
Response 201
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "email": "jamie@example.com",
  "first_name": "Jamie",
  "last_name": "Reed",
  "title": "Head of Growth"
}

Create or update a contact by email

PUT /t/{slug}/api/contacts/

Create-or-update, matched on email. Returns 201 when a contact was created and 200 when an existing one was updated — the status is how you tell which happened.

This is the endpoint an integration should reach for by default: it is idempotent on email, so a replayed sync converges instead of erroring.

On a match, custom_fields and subscriptions are merged key-by-key and tags is unioned with what is already there — an upsert adds tags, it never removes them. Use PUT /api/contacts/{contact_id} when you need tags replaced outright.

Stage handling. stage_id is applied unconditionally, on create and on update. stage_id_if_new is applied only when the contact is being created and is ignored otherwise — use it for signup flows that want a default stage without stamping over a stage a salesperson already set. If both are given, stage_id wins on creation. Changing an existing contact's stage runs the same score-reset logic as a direct update.

include_identity_token string

Return an identity_token for the client-side tracking SDK alongside the contact.

emailstringRequired
address_line1string · nullable
address_line2string · nullable
citystring · nullable
companystring · nullable
countrystring · nullable
custom_fieldsobject
do_not_contactboolean
external_idstring · nullable
first_namestring · nullable
last_namestring · nullable
phonestring · nullable
sourcestring · nullable
stage_idstring
stage_id_if_newstring
statestring · nullable
subscriptionsobject
tagsstring[]
timezonestring · nullable
titlestring · nullable
websitestring · nullable

The ContactWithIdentityToken object.

200

An existing contact was updated.

201

A new contact was created.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X PUT "https://api.sonarsend.com/t/acme/api/contacts" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jamie@example.com",
    "first_name": "Jamie",
    "last_name": "Reed",
    "title": "Head of Growth",
    "company": "Acme",
    "phone": "+1 555 0100"
  }'
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "email": "jamie@example.com",
  "first_name": "Jamie",
  "last_name": "Reed",
  "title": "Head of Growth"
}

Count contacts

GET /t/{slug}/api/contacts/count

How many contacts match a filter, without fetching them. Accepts the same status / search / filters / definition selectors as GET /api/contacts, so you can size an audience before acting on it.

status string

Filter by account-wide status.

search string

Free-text match across name, email and company.

filters string

JSON-encoded array of { field, operator, value } objects.

definition string

JSON-encoded segment definition, for smart filtering.

countinteger
200

The number of matching contacts.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/contacts/count" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "count": 0
}

Get a contact

GET /t/{slug}/api/contacts/{id}

Fetch one contact by its canonical ID. To look one up by email instead, use GET /api/contacts?search=….

id string Required

Canonical contact ID.

The Contact object.

200

The contact.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/contacts/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "email": "jamie@example.com",
  "first_name": "Jamie",
  "last_name": "Reed",
  "title": "Head of Growth"
}

Update a contact

PUT /t/{slug}/api/contacts/{id}

Partial update by contact ID — send only the fields you are changing; anything omitted is left alone.

custom_fields and subscriptions are merged key-by-key, so a partial map does not clear the keys it leaves out. tags is the exception — here it replaces the existing array, where the upsert endpoint unions it. Changing stage_id runs the stage transition's score-reset logic. Changing email clears the resolved mx_record / smtp_provider so the new domain is re-enriched. smtp_provider and mx_record are platform-resolved and ignored on write.

id string Required

Canonical contact ID.

address_line1string · nullable
address_line2string · nullable
citystring · nullable
companystring · nullable
countrystring · nullable
custom_fieldsobject
do_not_contactboolean
emailstring
external_idstring · nullable
first_namestring · nullable
last_namestring · nullable
phonestring · nullable
sourcestring · nullable
stage_idstring · nullable
statestring · nullable
subscriptionsobject
tagsstring[]
timezonestring · nullable
titlestring · nullable
websitestring · nullable

The Contact object.

200

The updated contact.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X PUT "https://api.sonarsend.com/t/acme/api/contacts/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jamie@example.com",
    "first_name": "Jamie",
    "last_name": "Reed",
    "title": "Head of Growth",
    "company": "Acme",
    "phone": "+1 555 0100"
  }'
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "email": "jamie@example.com",
  "first_name": "Jamie",
  "last_name": "Reed",
  "title": "Head of Growth"
}

Soft-delete a contact

DELETE /t/{slug}/api/contacts/{id}

A soft delete. The row is kept and its status becomes suppressed, so the contact stops receiving mail and drops out of audiences while its history stays intact for reporting. POST /api/contacts/{contact_id}/restore puts it back in the status it held before.

This is not erasure — for a GDPR request use POST /api/contacts/{contact_id}/anonymize, which scrubs the personal fields irreversibly.

Reach for a status or subscription change before a delete: it is usually what the situation actually calls for, and it is reversible without a second endpoint.

id string Required

Canonical contact ID.

204

The contact was suppressed.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X DELETE "https://api.sonarsend.com/t/acme/api/contacts/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY"

Restore a soft-deleted contact

POST /t/{slug}/api/contacts/{id}/restore

Undo a soft delete. The contact returns to whatever status it held before it was suppressed — not unconditionally to active, so a contact that had unsubscribed comes back unsubscribed.

id string Required

Canonical contact ID.

The Contact object.

200

The restored contact.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/contacts/9c21895d-57f0-4a15-a1df-4dc6835d4f80/restore" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "email": "jamie@example.com",
  "first_name": "Jamie",
  "last_name": "Reed",
  "title": "Head of Growth"
}

Merge contacts

POST /t/{slug}/api/contacts/merge

Folds up to ten duplicates into one surviving contact, then hard-deletes the secondaries — the rows are removed outright, not suppressed, and this cannot be undone. Treat it as a privileged action inside your integration and reserve it for dedupe work you fully control.

field_selections decides, per field, which record's value the survivor keeps. Values are contact IDs, not the words "primary"/"secondary": {"company": "<secondary_id>"} means the survivor takes that contact's company. Naming the primary — or omitting a field — keeps the primary's own value.

Selectable keys are email, first_name, last_name, title, company, phone, address_line1, address_line2, city, state, country, timezone, website, source, external_id, do_not_contact, global_status, stage_id, prospect_score, customer_score, plus subscriptions and custom.<field_key> for one custom field. A custom.<field_key> may also be set to the literal "merge", which unions that field's array values across all the contacts instead of picking one. smtp_provider and mx_record are platform-owned and are re-resolved from the surviving email.

field_selectionsobjectRequired

Field name to the ID of the contact whose value wins, e.g. {"company": "<secondary_id>"}. Fields you leave out keep the primary's value.

list_idsstring[]Required

IDs of static lists the survivor should end up on.

primary_iduuidRequired

The contact that survives the merge.

secondary_idsstring[]Required

IDs of the contacts folded into the primary. One to ten.

The Contact object.

200

The surviving contact, after the merge.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/contacts/merge" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "primary_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
    "secondary_ids": [
      "string"
    ],
    "field_selections": {},
    "list_ids": [
      "string"
    ]
  }'
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "tenant_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "email": "jamie@example.com",
  "first_name": "Jamie",
  "last_name": "Reed",
  "title": "Head of Growth"
}

Apply an action to many contacts

POST /t/{slug}/api/contacts/bulk

One call that changes many contacts, instead of one request each. If you are hitting the rate limit with per-record writes, this and PUT /api/contacts are the endpoints to move to.

Actions. delete (soft-delete to suppressed), suppress, restore, change_stage (needs stage_id), set_dnc (needs do_not_contact), reset_status, update_field (needs field and value) and enroll_in_sequence (needs enroll_in_sequence_id).

reset_status is not available to API keys and returns 403 ACTION_NOT_AVAILABLE. It clears unsubscribe and do-not-contact state across every targeted contact, which is a consent change we only accept from a signed-in operator. Every other action works normally.

Targeting. Every action except update_field requires an explicit contact_ids array; an empty or absent one returns MISSING_IDS. Only update_field can also target a filter. enroll_in_sequence is capped at 500 contacts per call.

Response shape depends on the action. enroll_in_sequence returns { enrolled, duplicate, skipped }; every other action returns { affected }.

Automation triggers do not fire by default. Pass trigger_automations: true on change_stage or a scalar update_field to emit the per-contact attribute-changed events that attribute_changed sequence entry triggers listen for.

actionenumRequired

What to do to the targeted contacts.

One of: delete, suppress, restore, change_stage, set_dnc, reset_status, update_field, enroll_in_sequence
contact_idsstring[]

Explicit target contact IDs. Required for every action except a filter-targeted update_field.

do_not_contactboolean

Required for set_dnc.

enroll_in_sequence_idstring

Sequence ID. Required for enroll_in_sequence.

fieldstring

Required for update_field. A column name, or custom.<field_key>.

filterobject

update_field only: target every contact matching this filter instead of an explicit list.

definitionany

A segment definition, for smart filtering.

filtersany[]

Array of { field, operator, value } objects.

merge_modeenum

update_field on an array field: append to the existing values, or replace them.

One of: add, replace
stage_idstring

Stage ID. Required for change_stage.

trigger_automationsboolean

Emit attribute-changed events for contacts that actually changed, so attribute_changed sequence triggers fire. Applies to change_stage and scalar update_field. Defaults to false.

valueany

Required for update_field. The value to set.

affectedinteger

Contacts changed. Present for every action except enroll_in_sequence.

duplicateinteger

enroll_in_sequence only: contacts already actively enrolled.

enrolledinteger

enroll_in_sequence only: contacts newly enrolled.

skippedinteger

enroll_in_sequence only: contacts the sequence would not accept.

200

Counts for the action that ran. enroll_in_sequence reports enrolment counts; every other action reports affected.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/contacts/bulk" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "delete",
    "contact_ids": [
      "string"
    ],
    "stage_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
    "do_not_contact": true,
    "filter": {
      "filters": [
        null
      ],
      "definition": null
    },
    "field": "string"
  }'
Response 200
{
  "affected": 0,
  "enrolled": 0,
  "duplicate": 0,
  "skipped": 0
}

List recent exports

GET /t/{slug}/api/contacts/export

The 30 most recent exports for the account, newest first — including expired ones, so you can tell "the file is gone" apart from "the export never ran". The download token is never included.

exportsobject[]
completed_atdate-time · nullable
configobject · nullable

The selectors the export was started with.

created_atdate-time
email_bounced_atdate-time · nullable
email_complained_atdate-time · nullable
email_sent_atdate-time · nullable

Always null for an API-key export — no requester means no notification email.

errorstring · nullable

Set when status is failed.

expires_atdate-time

After this the file is removed and download returns 410 EXPIRED.

iduuid

The export ID. Same value as export_id on the start response.

job_idstring · nullable
statusenum

Poll until this is completed, then download.

One of: queued, running, completed, failed, expired
total_contactsinteger · nullable

Rows written. Null until the job finishes.

200

Recent exports, newest first.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/contacts/export" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "exports": [
    {
      "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "job_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
      "status": "queued",
      "total_contacts": 0,
      "config": {},
      "error": "string"
    }
  ]
}

Start a contact export

POST /t/{slug}/api/contacts/export

Queues an asynchronous export and returns immediately with 202. This is the right way to read the whole audience out — one export produces one CSV, where paging GET /api/contacts would cost thousands of requests against your rate-limit budget.

Poll GET /api/contacts/export/{export_id} until status is completed, then fetch GET /api/contacts/export/{export_id}/download. An export started with an API key has no human requester, so no "ready" email is sent — polling is the only completion signal.

Exports expire. Once past expires_at the file is removed and download returns 410 EXPIRED; re-run the export if you need it again.

Requires contacts:read, not contacts:write: an export extracts data the key can already read and changes nothing.

definitionstring

JSON-encoded segment definition, for smart filtering.

fieldsstring[]

Column allow-list. Omit for the default set.

filtersany[]

Array of { field, operator, value } objects.

include_archivedboolean

Include archived contacts. Defaults to false.

include_lead_scoreboolean

Add the lead-score column. Defaults to false.

include_rollupsboolean

Add the engagement rollup columns. Defaults to false.

searchstring

Free-text match across name, email and company.

segment_iduuid

Restrict to the members of this list or segment.

statusstring

Restrict to one account-wide contact status.

export_iduuid
job_iduuid
statusenum
One of: queued
202

The export was queued. Poll the status endpoint with export_id.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X POST "https://api.sonarsend.com/t/acme/api/contacts/export" \
  -H "X-API-Key: $SONARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "segment_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
    "status": "active",
    "search": "string",
    "filters": [
      null
    ],
    "definition": "string",
    "fields": [
      "string"
    ]
  }'
Response 202
{
  "export_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "job_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "status": "queued"
}

Get export status

GET /t/{slug}/api/contacts/export/{exportId}

The polling endpoint. Keep calling until status is completed, then download; failed puts the reason in error, and expired means the file is gone and the export has to be re-run.

progress is a live row count while the job runs, and settles to the final total_contacts afterwards.

exportId string Required

The export_id from the start response.

completed_atdate-time · nullable
configobject · nullable

The selectors the export was started with.

created_atdate-time
email_bounced_atdate-time · nullable
email_complained_atdate-time · nullable
email_sent_atdate-time · nullable

Always null for an API-key export — no requester means no notification email.

errorstring · nullable

Set when status is failed.

expires_atdate-time

After this the file is removed and download returns 410 EXPIRED.

iduuid

The export ID. Same value as export_id on the start response.

job_idstring · nullable
progressinteger

Rows written so far; the final row count once the job has finished.

statusenum

Poll until this is completed, then download.

One of: queued, running, completed, failed, expired
total_contactsinteger · nullable

Rows written. Null until the job finishes.

200

The export's current state.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/contacts/export/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $SONARSEND_API_KEY"
Response 200
{
  "id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "job_id": "9c21895d-57f0-4a15-a1df-4dc6835d4f80",
  "status": "queued",
  "total_contacts": 0,
  "config": {},
  "error": "string"
}

Download an export

GET /t/{slug}/api/contacts/export/{exportId}/download

Streams the finished CSV as text/csv. Not JSON — the body is the file.

409 NOT_READY means the job has not finished; keep polling the status endpoint. 410 EXPIRED means the file is past its TTL and has been removed — re-run the export.

exportId string Required

The export_id from the start response.

200

The export CSV.

400

The request failed validation, or a referenced record was rejected.

404

The tenant slug, or the record the path names, does not exist.

Request
curl -X GET "https://api.sonarsend.com/t/acme/api/contacts/export/9c21895d-57f0-4a15-a1df-4dc6835d4f80/download" \
  -H "X-API-Key: $SONARSEND_API_KEY"