Docs
API reference 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.radarsend.com Auth · X-API-Key
The Contact object 64 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
full_namestring · nullable

The name as the person typed it, in one piece (e.g. a form's Full name field). first_name/last_name may be a best-guess split of it.

global_statusenum

Account-wide sendability. suppressed is set by a soft delete. pending is a contact created by the website identify() call that nobody has vouched for yet: it is tracked, but it cannot be mailed and does not count toward the plan. Creating or updating it through this API activates it (and is the point at which the plan limit applies).

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

Canonical contact ID. Store this.

last_activity_atdate-time · nullable
last_activity_in_reviewboolean

True when the click or page view behind last_activity_at has not been checked for scanners and bots yet. Derived at read time on the detail and list reads; absent on write responses.

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, pending, 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 66 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
full_namestring · nullable

The name as the person typed it, in one piece (e.g. a form's Full name field). first_name/last_name may be a best-guess split of it.

global_statusenum

Account-wide sendability. suppressed is set by a soft delete. pending is a contact created by the website identify() call that nobody has vouched for yet: it is tracked, but it cannot be mailed and does not count toward the plan. Creating or updating it through this API activates it (and is the point at which the plan limit applies).

One of: active, pending, 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_activity_in_reviewboolean

True when the click or page view behind last_activity_at has not been checked for scanners and bots yet. Derived at read time on the detail and list reads; absent on write responses.

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
session_linkenum

Present only when the request passed session_id. linked: the browser session is now this contact's, and its earlier page views are being attributed. already_linked: it already was — nothing changed. failed: the contact was written but the session could not be linked; repeat the call with PUT to retry the link.

One of: linked, already_linked, failed
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, pending, 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.radarsend.com/t/acme/api/contacts" \
  -H "X-API-Key: $RADARSEND_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",
      "full_name": "Acme onboarding"
    }
  ],
  "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.

Over the plan. When a create leaves the workspace over its plan's included contacts, the contact is still created and the response carries X-Contacts-Over-Limit: true, X-Contact-Limit and X-Active-Contacts. The extra contacts are billed as overage. At the plan's contact ceiling the create fails with 429 QUOTA_EXCEEDED.

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
full_namestring · nullable
last_namestring · nullable
phonestring · nullable
postal_codestring · nullable
session_idstring · nullable

The visitor's _emts tracking cookie, exactly as your server received it on the visitor's request. Links that browser session to this contact: its earlier anonymous page views are attributed to the contact, and so is everything the browser does afterwards. Must be a value the tracking script issued — anything else is refused with 400 INVALID_SESSION_ID before the contact is written. null (the visitor has no cookie) is the same as leaving it out.

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.radarsend.com/t/acme/api/contacts" \
  -H "X-API-Key: $RADARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jamie@example.com",
    "first_name": "Jamie",
    "last_name": "Reed",
    "full_name": "Acme onboarding",
    "title": "Head of Growth",
    "company": "Acme"
  }'
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",
  "full_name": "Acme onboarding"
}

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 — or when this call activates a Pending contact that has no stage yet (one a browser identify() 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. Changing an existing contact's stage runs the same score-reset logic as a direct update.

Over the plan. A create that leaves the workspace over its plan's included contacts returns X-Contacts-Over-Limit: true with X-Contact-Limit and X-Active-Contacts, as POST /api/contacts does.

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
full_namestring · nullable
last_namestring · nullable
phonestring · nullable
postal_codestring · nullable
session_idstring · nullable

The visitor's _emts tracking cookie, exactly as your server received it on the visitor's request. Links that browser session to this contact: its earlier anonymous page views are attributed to the contact, and so is everything the browser does afterwards. Must be a value the tracking script issued — anything else is refused with 400 INVALID_SESSION_ID before the contact is written. null (the visitor has no cookie) is the same as leaving it out.

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.radarsend.com/t/acme/api/contacts" \
  -H "X-API-Key: $RADARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jamie@example.com",
    "first_name": "Jamie",
    "last_name": "Reed",
    "full_name": "Acme onboarding",
    "title": "Head of Growth",
    "company": "Acme"
  }'
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",
  "full_name": "Acme onboarding"
}

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.

include_pending string

Also return pending: how many of the matching contacts are pending (tracked, but not yet mailable or billed). One request, so the two numbers describe the same moment.

countinteger
pendinginteger
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.radarsend.com/t/acme/api/contacts/count" \
  -H "X-API-Key: $RADARSEND_API_KEY"
Response 200
{
  "count": 0,
  "pending": 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.radarsend.com/t/acme/api/contacts/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $RADARSEND_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",
  "full_name": "Acme onboarding"
}

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
full_namestring · nullable
last_namestring · nullable
phonestring · nullable
postal_codestring · 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.radarsend.com/t/acme/api/contacts/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $RADARSEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jamie@example.com",
    "first_name": "Jamie",
    "last_name": "Reed",
    "full_name": "Acme onboarding",
    "title": "Head of Growth",
    "company": "Acme"
  }'
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",
  "full_name": "Acme onboarding"
}

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.radarsend.com/t/acme/api/contacts/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $RADARSEND_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.radarsend.com/t/acme/api/contacts/9c21895d-57f0-4a15-a1df-4dc6835d4f80/restore" \
  -H "X-API-Key: $RADARSEND_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",
  "full_name": "Acme onboarding"
}

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, full_name, title, company, phone, address_line1, address_line2, city, state, postal_code, 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.radarsend.com/t/acme/api/contacts/merge" \
  -H "X-API-Key: $RADARSEND_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",
  "full_name": "Acme onboarding"
}

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, activate_pending, 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.radarsend.com/t/acme/api/contacts/bulk" \
  -H "X-API-Key: $RADARSEND_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.radarsend.com/t/acme/api/contacts/export" \
  -H "X-API-Key: $RADARSEND_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.radarsend.com/t/acme/api/contacts/export" \
  -H "X-API-Key: $RADARSEND_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.radarsend.com/t/acme/api/contacts/export/9c21895d-57f0-4a15-a1df-4dc6835d4f80" \
  -H "X-API-Key: $RADARSEND_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.radarsend.com/t/acme/api/contacts/export/9c21895d-57f0-4a15-a1df-4dc6835d4f80/download" \
  -H "X-API-Key: $RADARSEND_API_KEY"