Developers
The API, generated from the code that runs it
A cursor-paged REST API over contacts, lists, tags, segments, campaigns and automations.
https://mintmailer.app/api/v1Every path is relative to it, so /contacts is https://mintmailer.app/api/v1/contacts.Getting started
Authentication. Send your key as Authorization: Bearer rvz_live_…. A key belongs to one organisation and carries a fixed set of scopes, chosen when it was created; every operation states the scope it requires. A key without the scope gets 403, and a request for another organisation's row gets 404 rather than 403 — confirming that an id exists elsewhere is itself a disclosure.
Pagination. List endpoints return {data, has_more, next_cursor}. Pass next_cursor back as cursor to walk forward; the default page is 50 rows and the maximum is 100. The cursor is a keyset, not an offset, so a row inserted while you page cannot cause a skip or a repeat — which is what makes an incremental sync safe. It is opaque: pass it back unmodified and do not parse it.
Idempotency. Send an Idempotency-Key header on any write and a retry of that exact request returns the first response byte for byte, including its status and any id, rather than creating a second row. Idempotent-Replay on the response says which of the two happened. Omitting the header is allowed and gives you ordinary at-least-once semantics.
Rate limits. Per key, not per organisation, so one integration cannot spend another's budget. RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset are on every response, so you never have to exceed the limit to discover it.
Webhooks. Rather than polling, register an endpoint at POST /webhooks and we will POST each event you subscribe to as it happens: email.delivered, email.bounced, email.complained, email.opened, email.clicked, contact.unsubscribed and campaign.finished.
Every request carries Mintmail-Signature: t=<unix seconds>,v1=<hex>, an HMAC-SHA256 over ${t}.${body} keyed with the secret shown once when the endpoint was created. Verify it against the RAW body before parsing — re-serialising JSON changes the bytes and the signature will not match. Reject anything whose t is more than five minutes old, or a payload captured off the wire can be replayed at leisure.
Delivery is at-least-once and unordered. The event stream underneath does not order its messages, so an email.opened can reach you before the email.delivered it belongs to, and any event can arrive twice — after a retry, or after our own reconciliation notices a delivery went missing. Key your handler's idempotency on the payload's top-level id, which is stable for the life of the event. We do not promise ordering because the transport cannot keep that promise.
Answer 2xx to acknowledge. Anything else is retried six times over roughly a quarter of an hour with exponential backoff; ten consecutive events that exhaust their retries disable the endpoint and email your admins. Answer 410 Gone to unsubscribe permanently — we stop immediately and do not retry.
Webhook payload. The Webhook object in the reference is the endpoint subscription (URL + events). The body we POST to your URL is a different shape: WebhookEvent — see Object reference. Every delivery is {id, type, occurred_at, organization_id, data}; message and opt-out events include data.email so you can match the person without calling the API back.
Message events (email.*) put campaign_id, contact_id, email, message_recipient_id and machine in data (clicks add link_url; bounces add bounce_type, bounce_subtype, diagnostic_code). contact.unsubscribed is {contact_id, email, campaign_id?}. campaign.finished is {campaign_id, name, sent, failed, total} with no contact fields.
Versioning. v1 changes additively only: an endpoint, an optional field or a new enum value may appear at any time, and nothing is ever removed, renamed or retyped inside a major version. Parse leniently and a release cannot break you. No version is withdrawn on less than twelve months' notice, announced with a Sunset header on every affected response long before it stops answering.
Automations
/automationsautomation:viewList automations
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
status | ("draft" | "active" | "paused" | "archived") | ("draft" | "active" | "paused" | "archived")[] |
200 page of Automation in dataErrors: 400, 401, 403, 429, 500Example response
{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "automation",
"name": "Weekly digest",
"status": "draft",
"version": 1,
"allow_reentry": true,
"definition": {
"version": 1,
"entryNodeId": "entryNodeId",
"nodes": {}
},
"activated_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/automations/{id}automation:viewRetrieve an automation
Path parameters
idrequired | string | The automation's id. |
200 Automation in dataErrors: 400, 401, 403, 404, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "automation",
"name": "Weekly digest",
"status": "draft",
"version": 1,
"allow_reentry": true,
"definition": {
"version": 1,
"entryNodeId": "entryNodeId",
"nodes": {}
},
"activated_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/automations/{id}/activateautomation:editTurn an automation on, or resume a paused one
Path parameters
idrequired | string | The automation's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
200 Automation in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "automation",
"name": "Weekly digest",
"status": "draft",
"version": 1,
"allow_reentry": true,
"definition": {
"version": 1,
"entryNodeId": "entryNodeId",
"nodes": {}
},
"activated_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/automations/{id}/enrollautomation:editEnrol contacts into an active automation
Path parameters
idrequired | string | The automation's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
contact_idsrequired | string[] |
200 Enrollment in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500Example response
{
"data": {
"object": "enrollment",
"requested": 128,
"enrolled": 128
}
}/automations/{id}/pauseautomation:editPause an automation, holding everyone inside it
Path parameters
idrequired | string | The automation's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
200 Automation in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "automation",
"name": "Weekly digest",
"status": "draft",
"version": 1,
"allow_reentry": true,
"definition": {
"version": 1,
"entryNodeId": "entryNodeId",
"nodes": {}
},
"activated_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}Campaigns
/campaignscampaign:viewList campaigns
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
status | ("draft" | "scheduled" | "dispatching" | "sending" | "testing" | "paused" | "sent" | "cancelled" | "failed") | ("draft" | "scheduled" | "dispatching" | "sending" | "testing" | "paused" | "sent" | "cancelled" | "failed")[] |
200 page of Campaign in dataErrors: 400, 401, 403, 429, 500Example response
{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "campaign",
"name": "Weekly digest",
"status": "draft",
"subject": "Your July statement",
"preheader": "Everything that moved this month, in one place.",
"sender_identity_id": "cly3k9f2b0000q8n1h4d7g5xz",
"from_name": "Acme",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"email_document_id": "cly3k9f2b0000q8n1h4d7g5xz",
"audience": {
"include": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
},
"exclude": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
}
},
"track_opens": true,
"track_clicks": true,
"scheduled_at": "2026-07-14T09:31:02.000Z",
"send_started_at": "2026-07-14T09:31:02.000Z",
"send_completed_at": "2026-07-14T09:31:02.000Z",
"recipient_count": 128,
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/campaignscampaign:editCreate a campaign
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
namerequired | string |
201 Campaign in dataErrors: 400, 401, 403, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "campaign",
"name": "Weekly digest",
"status": "draft",
"subject": "Your July statement",
"preheader": "Everything that moved this month, in one place.",
"sender_identity_id": "cly3k9f2b0000q8n1h4d7g5xz",
"from_name": "Acme",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"email_document_id": "cly3k9f2b0000q8n1h4d7g5xz",
"audience": {
"include": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
},
"exclude": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
}
},
"track_opens": true,
"track_clicks": true,
"scheduled_at": "2026-07-14T09:31:02.000Z",
"send_started_at": "2026-07-14T09:31:02.000Z",
"send_completed_at": "2026-07-14T09:31:02.000Z",
"recipient_count": 128,
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/campaigns/{id}campaign:viewRetrieve a campaign
Path parameters
idrequired | string | The campaign's id. |
200 Campaign in dataErrors: 400, 401, 403, 404, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "campaign",
"name": "Weekly digest",
"status": "draft",
"subject": "Your July statement",
"preheader": "Everything that moved this month, in one place.",
"sender_identity_id": "cly3k9f2b0000q8n1h4d7g5xz",
"from_name": "Acme",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"email_document_id": "cly3k9f2b0000q8n1h4d7g5xz",
"audience": {
"include": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
},
"exclude": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
}
},
"track_opens": true,
"track_clicks": true,
"scheduled_at": "2026-07-14T09:31:02.000Z",
"send_started_at": "2026-07-14T09:31:02.000Z",
"send_completed_at": "2026-07-14T09:31:02.000Z",
"recipient_count": 128,
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/campaigns/{id}campaign:editUpdate a draft campaign
Path parameters
idrequired | string | The campaign's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
name | string | |
subject | string | null | |
preheader | string | null | |
sender_identity_id | string | |
reply_to | "" | string | null | |
email_document_id | string | |
audience | object | |
include | object | |
exclude | object | |
track_opens | boolean | |
track_clicks | boolean |
200 Campaign in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "campaign",
"name": "Weekly digest",
"status": "draft",
"subject": "Your July statement",
"preheader": "Everything that moved this month, in one place.",
"sender_identity_id": "cly3k9f2b0000q8n1h4d7g5xz",
"from_name": "Acme",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"email_document_id": "cly3k9f2b0000q8n1h4d7g5xz",
"audience": {
"include": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
},
"exclude": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
}
},
"track_opens": true,
"track_clicks": true,
"scheduled_at": "2026-07-14T09:31:02.000Z",
"send_started_at": "2026-07-14T09:31:02.000Z",
"send_completed_at": "2026-07-14T09:31:02.000Z",
"recipient_count": 128,
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/campaigns/{id}campaign:deleteDelete a draft or cancelled campaign
Path parameters
idrequired | string | The campaign's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
204 no bodyErrors: 400, 401, 403, 404, 409, 422, 429, 500/campaigns/{id}/cancelcampaign:sendCancel a scheduled or sending campaign
Path parameters
idrequired | string | The campaign's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
200 Campaign in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "campaign",
"name": "Weekly digest",
"status": "draft",
"subject": "Your July statement",
"preheader": "Everything that moved this month, in one place.",
"sender_identity_id": "cly3k9f2b0000q8n1h4d7g5xz",
"from_name": "Acme",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"email_document_id": "cly3k9f2b0000q8n1h4d7g5xz",
"audience": {
"include": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
},
"exclude": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
}
},
"track_opens": true,
"track_clicks": true,
"scheduled_at": "2026-07-14T09:31:02.000Z",
"send_started_at": "2026-07-14T09:31:02.000Z",
"send_completed_at": "2026-07-14T09:31:02.000Z",
"recipient_count": 128,
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/campaigns/{id}/schedulecampaign:sendSchedule a campaign, or send it now
Path parameters
idrequired | string | The campaign's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
scheduled_at | string | When to send, as an ISO 8601 instant. Omit to send now. |
200 Campaign in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "campaign",
"name": "Weekly digest",
"status": "draft",
"subject": "Your July statement",
"preheader": "Everything that moved this month, in one place.",
"sender_identity_id": "cly3k9f2b0000q8n1h4d7g5xz",
"from_name": "Acme",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"email_document_id": "cly3k9f2b0000q8n1h4d7g5xz",
"audience": {
"include": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
},
"exclude": {
"list_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"tag_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
],
"segment_ids": [
"cly3k9f2b0000q8n1h4d7g5xz"
]
}
},
"track_opens": true,
"track_clicks": true,
"scheduled_at": "2026-07-14T09:31:02.000Z",
"send_started_at": "2026-07-14T09:31:02.000Z",
"send_completed_at": "2026-07-14T09:31:02.000Z",
"recipient_count": 128,
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/campaigns/{id}/statsanalytics:viewRetrieve a campaign's engagement totals and rates
Path parameters
idrequired | string | The campaign's id. |
200 CampaignStats in dataErrors: 400, 401, 403, 404, 429, 500Example response
{
"data": {
"object": "campaign_stats",
"campaign_id": "cly3k9f2b0000q8n1h4d7g5xz",
"sent": 128,
"delivered": 128,
"opened": 128,
"unique_opened": 128,
"clicked": 128,
"unique_clicked": 128,
"bounced": 128,
"soft_bounced": 128,
"complained": 128,
"unsubscribed": 128,
"failed": 128,
"open_rate": 0.42,
"click_rate": 0.42,
"click_to_open_rate": 0.42,
"bounce_rate": 0.42,
"hard_bounce_rate": 0.42,
"complaint_rate": 0.42,
"unsubscribe_rate": 0.42,
"updated_at": "2026-07-14T09:31:02.000Z"
}
}Contacts
/contactscontact:viewList contacts
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
status | ("subscribed" | "unsubscribed" | "bounced" | "complained" | "pending") | ("subscribed" | "unsubscribed" | "bounced" | "complained" | "pending")[] | |
list_id | string | string[] | |
tag_id | string | string[] | |
search | string |
200 page of Contact in dataErrors: 400, 401, 403, 429, 500Example response
{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "contact",
"email": "[email protected]",
"first_name": "Jamie",
"last_name": "Rivera",
"status": "subscribed",
"custom_fields": {
"plan": "Pro",
"signup_source": "webinar"
},
"opt_in_source": "signup_form",
"opt_in_at": "2026-07-14T09:31:02.000Z",
"last_engaged_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/contactscontact:editCreate or update a contact
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
emailrequired | string | |
first_name | string | null | |
last_name | string | null | |
opt_in_source | string | null | |
status | "subscribed" | "unsubscribed" | "bounced" | "complained" | "pending" |
200 or 201 Contact in dataErrors: 400, 401, 403, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "contact",
"email": "[email protected]",
"first_name": "Jamie",
"last_name": "Rivera",
"status": "subscribed",
"custom_fields": {
"plan": "Pro",
"signup_source": "webinar"
},
"opt_in_source": "signup_form",
"opt_in_at": "2026-07-14T09:31:02.000Z",
"last_engaged_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/contacts/{id}contact:viewRetrieve a contact
Path parameters
idrequired | string | The contact's id. |
200 Contact in dataErrors: 400, 401, 403, 404, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "contact",
"email": "[email protected]",
"first_name": "Jamie",
"last_name": "Rivera",
"status": "subscribed",
"custom_fields": {
"plan": "Pro",
"signup_source": "webinar"
},
"opt_in_source": "signup_form",
"opt_in_at": "2026-07-14T09:31:02.000Z",
"last_engaged_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/contacts/{id}contact:editUpdate a contact
Path parameters
idrequired | string | The contact's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
email | string | |
first_name | string | null | |
last_name | string | null | |
opt_in_source | string | null | |
status | "subscribed" | "unsubscribed" | "bounced" | "complained" | "pending" |
200 Contact in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "contact",
"email": "[email protected]",
"first_name": "Jamie",
"last_name": "Rivera",
"status": "subscribed",
"custom_fields": {
"plan": "Pro",
"signup_source": "webinar"
},
"opt_in_source": "signup_form",
"opt_in_at": "2026-07-14T09:31:02.000Z",
"last_engaged_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/contacts/{id}contact:deleteDelete a contact
Path parameters
idrequired | string | The contact's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
204 no bodyErrors: 400, 401, 403, 404, 409, 422, 429, 500/contacts/{id}/listslist:viewList the lists a contact belongs to
Path parameters
idrequired | string | The contact's id. |
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
200 page of List in dataErrors: 400, 401, 403, 404, 429, 500Example response
{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "list",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}Custom fields
/custom_fieldscontact:viewList the organisation's custom field definitions
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
200 page of CustomField in dataErrors: 400, 401, 403, 429, 500Example response
{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "custom_field",
"key": "plan",
"label": "Plan",
"type": "text",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}Lists
/listslist:viewList contact lists
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
name | string |
200 page of List in dataErrors: 400, 401, 403, 429, 500Example response
{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "list",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/listslist:editCreate a contact list
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
namerequired | string | |
description | string | null |
201 List in dataErrors: 400, 401, 403, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "list",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/lists/{id}list:viewRetrieve a contact list
Path parameters
idrequired | string | The list's id. |
200 List in dataErrors: 400, 401, 403, 404, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "list",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/lists/{id}list:editUpdate a contact list
Path parameters
idrequired | string | The list's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
name | string | |
description | string | null |
200 List in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "list",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/lists/{id}list:editDelete a contact list, keeping its contacts
Path parameters
idrequired | string | The list's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
204 no bodyErrors: 400, 401, 403, 404, 409, 422, 429, 500/lists/{id}/contactscontact:viewList the contacts on a list
Path parameters
idrequired | string | The list's id. |
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
200 page of Contact in dataErrors: 400, 401, 403, 404, 429, 500Example response
{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "contact",
"email": "[email protected]",
"first_name": "Jamie",
"last_name": "Rivera",
"status": "subscribed",
"custom_fields": {
"plan": "Pro",
"signup_source": "webinar"
},
"opt_in_source": "signup_form",
"opt_in_at": "2026-07-14T09:31:02.000Z",
"last_engaged_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/lists/{id}/contactslist:editAdd contacts to a list
Path parameters
idrequired | string | The list's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
contact_idsrequired | string[] |
200 MembershipChange in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500Example response
{
"data": {
"object": "membership_change",
"changed": 128
}
}/lists/{id}/contactslist:editRemove contacts from a list
Path parameters
idrequired | string | The list's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
contact_idsrequired | string[] |
200 MembershipChange in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500Example response
{
"data": {
"object": "membership_change",
"changed": 128
}
}Orders
/ordersorder:viewList orders
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
email | string | |
status | "paid" | "refunded" | "partially_refunded" | "cancelled" | |
attributed | "true" | "false" |
200 page of Order in dataErrors: 400, 401, 403, 429, 500Example response
{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "order",
"external_id": "cly3k9f2b0000q8n1h4d7g5xz",
"email": "[email protected]",
"contact_id": "cly3k9f2b0000q8n1h4d7g5xz",
"status": "paid",
"total_cents": 128,
"refunded_cents": 128,
"currency": "currency",
"occurred_at": "2026-07-14T09:31:02.000Z",
"source": "source",
"attributed_campaign_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attributed_automation_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attributed_node_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attribution_rule": "attribution_rule",
"attributed_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/ordersorder:editCreate or update an order
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
external_idrequired | string | |
emailrequired | string | |
status | "paid" | "refunded" | "partially_refunded" | "cancelled" | |
total_centsrequired | integer | |
refunded_cents | integer | |
currencyrequired | string | |
occurred_atrequired | string | |
source | string | null |
200 or 201 Order in dataErrors: 400, 401, 403, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "order",
"external_id": "cly3k9f2b0000q8n1h4d7g5xz",
"email": "[email protected]",
"contact_id": "cly3k9f2b0000q8n1h4d7g5xz",
"status": "paid",
"total_cents": 128,
"refunded_cents": 128,
"currency": "currency",
"occurred_at": "2026-07-14T09:31:02.000Z",
"source": "source",
"attributed_campaign_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attributed_automation_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attributed_node_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attribution_rule": "attribution_rule",
"attributed_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/orders/{id}order:viewRetrieve an order
Path parameters
idrequired | string | The order's id. |
200 Order in dataErrors: 400, 401, 403, 404, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "order",
"external_id": "cly3k9f2b0000q8n1h4d7g5xz",
"email": "[email protected]",
"contact_id": "cly3k9f2b0000q8n1h4d7g5xz",
"status": "paid",
"total_cents": 128,
"refunded_cents": 128,
"currency": "currency",
"occurred_at": "2026-07-14T09:31:02.000Z",
"source": "source",
"attributed_campaign_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attributed_automation_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attributed_node_id": "cly3k9f2b0000q8n1h4d7g5xz",
"attribution_rule": "attribution_rule",
"attributed_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}Segments
/segmentssegment:viewList segments
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
name | string |
200 page of Segment in dataErrors: 400, 401, 403, 429, 500Example response
{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "segment",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"rules": {
"version": 1,
"match": "all",
"groups": [
"groups"
]
},
"last_count": 128,
"last_counted_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/segmentssegment:editCreate a segment
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
namerequired | string | |
description | string | null | |
rulesrequired | any |
201 Segment in dataErrors: 400, 401, 403, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "segment",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"rules": {
"version": 1,
"match": "all",
"groups": [
"groups"
]
},
"last_count": 128,
"last_counted_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/segments/{id}segment:viewRetrieve a segment
Path parameters
idrequired | string | The segment's id. |
200 Segment in dataErrors: 400, 401, 403, 404, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "segment",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"rules": {
"version": 1,
"match": "all",
"groups": [
"groups"
]
},
"last_count": 128,
"last_counted_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/segments/{id}segment:editUpdate a segment
Path parameters
idrequired | string | The segment's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
name | string | |
description | string | null | |
rules | any |
200 Segment in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "segment",
"name": "Weekly digest",
"description": "Everyone who opened something in the last 90 days.",
"rules": {
"version": 1,
"match": "all",
"groups": [
"groups"
]
},
"last_count": 128,
"last_counted_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/segments/{id}segment:editDelete a segment
Path parameters
idrequired | string | The segment's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
204 no bodyErrors: 400, 401, 403, 404, 409, 422, 429, 500/segments/{id}/contactscontact:viewList the contacts a segment selects
Path parameters
idrequired | string | The segment's id. |
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
200 page of Contact in dataErrors: 400, 401, 403, 404, 429, 500Example response
{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "contact",
"email": "[email protected]",
"first_name": "Jamie",
"last_name": "Rivera",
"status": "subscribed",
"custom_fields": {
"plan": "Pro",
"signup_source": "webinar"
},
"opt_in_source": "signup_form",
"opt_in_at": "2026-07-14T09:31:02.000Z",
"last_engaged_at": "2026-07-14T09:31:02.000Z",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}Webhooks
/webhookswebhook:manageList webhook endpoints
Query parameters
cursor | string | next_cursor from the previous page, passed back unmodified. |
limit | integer | Rows per page, 1–100. Defaults to 50. |
200 page of Webhook in dataErrors: 400, 401, 403, 429, 500Example response
{
"data": [
{
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "webhook",
"url": "url",
"description": "Everyone who opened something in the last 90 days.",
"events": [
"email.delivered"
],
"status": "enabled",
"disabled_reason": "disabled_reason",
"consecutive_failures": 128,
"last_success_at": "2026-07-14T09:31:02.000Z",
"last_failure_at": "2026-07-14T09:31:02.000Z",
"secret": "secret",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
],
"has_more": false,
"next_cursor": null
}/webhookswebhook:manageCreate a webhook endpoint
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
urlrequired | string | |
eventsrequired | ("email.delivered" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "contact.unsubscribed" | "campaign.finished")[] | |
description | string |
201 Webhook in dataErrors: 400, 401, 403, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "webhook",
"url": "url",
"description": "Everyone who opened something in the last 90 days.",
"events": [
"email.delivered"
],
"status": "enabled",
"disabled_reason": "disabled_reason",
"consecutive_failures": 128,
"last_success_at": "2026-07-14T09:31:02.000Z",
"last_failure_at": "2026-07-14T09:31:02.000Z",
"secret": "secret",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}Outbound event delivery
The JSON body we later POST to your URL — not the create response above. See WebhookEvent for the full field list.
Example delivery
{
"id": "evt_01hxyz",
"type": "email.opened",
"occurred_at": "2026-08-03T10:30:00.000Z",
"organization_id": "org_01hxyz",
"data": {
"campaign_id": "cmp_01hxyz",
"contact_id": "con_01hxyz",
"email": "[email protected]",
"message_recipient_id": "mr_01hxyz",
"machine": false
}
}Delivery body
idrequired | string | Stable source-event id. Delivery is at-least-once — key your idempotency on this. |
typerequired | "email.delivered" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "contact.unsubscribed" | "campaign.finished" | The dotted event name you subscribed to, e.g. email.opened. |
occurred_atrequired | string | When the event happened, not when we delivered it. |
organization_idrequired | string | Your organisation. |
datarequired | object | Message-event fields. For contact.unsubscribed, data is {contact_id, email, campaign_id?}. For campaign.finished, data is {campaign_id, name, sent, failed, total} — no contact fields. |
campaign_idrequired | string | The campaign that sent the message. |
contact_idrequired | string | Our id for the person. |
emailrequired | string | null | The contact's address — match this to a contact in your system without calling the API back. Null only if the contact was deleted between the event and the emit. |
message_recipient_idrequired | string | This specific send of the message. |
machinerequired | boolean | True when an open or click came from a scanner or link unfurler rather than a person. Always present on message events, including when false. Machine hits are excluded from every figure in the app. |
link_url | string | null | Clicks only — the URL that was clicked. |
bounce_type | string | null | Bounces only — permanent, transient, or undetermined. |
bounce_subtype | string | null | Bounces only — provider subtype when available. |
diagnostic_code | string | null | Bounces only — SMTP diagnostic when available. |
/webhooks/{id}webhook:manageRetrieve a webhook endpoint
Path parameters
idrequired | string | The webhook's id. |
200 Webhook in dataErrors: 400, 401, 403, 404, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "webhook",
"url": "url",
"description": "Everyone who opened something in the last 90 days.",
"events": [
"email.delivered"
],
"status": "enabled",
"disabled_reason": "disabled_reason",
"consecutive_failures": 128,
"last_success_at": "2026-07-14T09:31:02.000Z",
"last_failure_at": "2026-07-14T09:31:02.000Z",
"secret": "secret",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/webhooks/{id}webhook:manageUpdate a webhook endpoint
Path parameters
idrequired | string | The webhook's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
Request body
url | string | |
events | ("email.delivered" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "contact.unsubscribed" | "campaign.finished")[] | |
description | string | null | |
status | "enabled" | "disabled_by_user" |
200 Webhook in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500Example response
{
"data": {
"id": "cly3k9f2b0000q8n1h4d7g5xz",
"object": "webhook",
"url": "url",
"description": "Everyone who opened something in the last 90 days.",
"events": [
"email.delivered"
],
"status": "enabled",
"disabled_reason": "disabled_reason",
"consecutive_failures": 128,
"last_success_at": "2026-07-14T09:31:02.000Z",
"last_failure_at": "2026-07-14T09:31:02.000Z",
"secret": "secret",
"created_at": "2026-07-14T09:31:02.000Z",
"updated_at": "2026-07-14T09:31:02.000Z"
}
}/webhooks/{id}webhook:manageDelete a webhook endpoint
Path parameters
idrequired | string | The webhook's id. |
Headers
Idempotency-Key | string | A unique string of your choosing. Retrying a request with the same key returns the first response byte for byte — same status, same ids — instead of doing the work twice. Scoped to this key, this method and this path. |
204 no bodyErrors: 400, 401, 403, 404, 409, 422, 429, 500Object reference
Every REST resource carries an object field naming its type, so a client holding one response can tell what it has without inferring from the shape. WebhookEvent is the outbound push body (no object field) — what we POST to your endpoint, distinct from the Webhook subscription resource.
Automation
Fields
idrequired | string | |
objectrequired | "automation" | |
namerequired | string | |
statusrequired | "draft" | "active" | "paused" | "archived" | |
versionrequired | integer | |
allow_reentryrequired | boolean | |
definitionrequired | object | The flow, in the grammar automationDefinitionSchema defines. Read-only in v1. |
versionrequired | integer | |
entryNodeIdrequired | string | null | |
nodesrequired | object | |
activated_atrequired | string | null | |
created_atrequired | string | |
updated_atrequired | string |
Campaign
Fields
idrequired | string | |
objectrequired | "campaign" | |
namerequired | string | |
statusrequired | "draft" | "scheduled" | "dispatching" | "sending" | "testing" | "paused" | "sent" | "cancelled" | "failed" | |
subjectrequired | string | null | |
preheaderrequired | string | null | |
sender_identity_idrequired | string | null | |
from_namerequired | string | null | |
from_emailrequired | string | null | |
reply_torequired | string | null | |
email_document_idrequired | string | null | |
audiencerequired | object | |
includerequired | object | |
excluderequired | object | |
track_opensrequired | boolean | |
track_clicksrequired | boolean | |
scheduled_atrequired | string | null | |
send_started_atrequired | string | null | |
send_completed_atrequired | string | null | |
recipient_countrequired | integer | |
created_atrequired | string | |
updated_atrequired | string |
CampaignStats
Fields
objectrequired | "campaign_stats" | |
campaign_idrequired | string | |
sentrequired | integer | |
deliveredrequired | integer | |
openedrequired | integer | |
unique_openedrequired | integer | |
clickedrequired | integer | |
unique_clickedrequired | integer | |
bouncedrequired | integer | |
soft_bouncedrequired | integer | |
complainedrequired | integer | |
unsubscribedrequired | integer | |
failedrequired | integer | |
open_raterequired | number | null | |
click_raterequired | number | null | |
click_to_open_raterequired | number | null | |
bounce_raterequired | number | null | |
hard_bounce_raterequired | number | null | |
complaint_raterequired | number | null | |
unsubscribe_raterequired | number | null | |
updated_atrequired | string | null |
Contact
Fields
idrequired | string | |
objectrequired | "contact" | |
emailrequired | string | |
first_namerequired | string | null | |
last_namerequired | string | null | |
statusrequired | "subscribed" | "unsubscribed" | "bounced" | "complained" | "pending" | |
custom_fieldsrequired | object | |
opt_in_sourcerequired | string | null | |
opt_in_atrequired | string | null | |
last_engaged_atrequired | string | null | |
created_atrequired | string | |
updated_atrequired | string |
CustomField
Fields
idrequired | string | |
objectrequired | "custom_field" | |
keyrequired | string | |
labelrequired | string | |
typerequired | "text" | "number" | "date" | "boolean" | |
created_atrequired | string | |
updated_atrequired | string |
Enrollment
Fields
objectrequired | "enrollment" | |
requestedrequired | integer | |
enrolledrequired | integer |
Error
Every 4xx and 5xx response has this shape, whatever went wrong.
Fields
errorrequired | object | |
typerequired | "invalid_request_error" | "authentication_error" | "permission_error" | "rate_limit_error" | "idempotency_error" | "api_error" | The coarse class. Switch on this, not on code. |
coderequired | "parameter_invalid" | "parameter_missing" | "malformed_request" | "api_key_missing" | "api_key_invalid" | "api_key_revoked" | "api_key_expired" | "insufficient_scope" | "resource_missing" | "resource_conflict" | "idempotency_key_in_flight" | "unprocessable" | "rate_limit_exceeded" | "internal_error" | The specific reason, for a human and for a log. |
messagerequired | string | |
param | string | The offending field, as a dotted path. Present when one field is at fault. |
request_idrequired | string | Also returned in X-Request-Id. Quote it in a support message and we can find exactly this request. |
List
Fields
idrequired | string | |
objectrequired | "list" | |
namerequired | string | |
descriptionrequired | string | null | |
created_atrequired | string | |
updated_atrequired | string |
MembershipChange
Fields
objectrequired | "membership_change" | |
changedrequired | integer |
Order
Fields
idrequired | string | |
objectrequired | "order" | |
external_idrequired | string | |
emailrequired | string | |
contact_idrequired | string | null | |
statusrequired | "paid" | "refunded" | "partially_refunded" | "cancelled" | |
total_centsrequired | integer | |
refunded_centsrequired | integer | |
currencyrequired | string | |
occurred_atrequired | string | |
sourcerequired | string | null | |
attributed_campaign_idrequired | string | null | |
attributed_automation_idrequired | string | null | |
attributed_node_idrequired | string | null | |
attribution_rulerequired | string | null | |
attributed_atrequired | string | null | |
created_atrequired | string | |
updated_atrequired | string |
Segment
Fields
idrequired | string | |
objectrequired | "segment" | |
namerequired | string | |
descriptionrequired | string | null | |
rulesrequired | object | The saved rule tree, in the grammar segmentRulesSchema defines. Pass it back unchanged to leave it alone. |
versionrequired | integer | |
matchrequired | "all" | "any" | |
groupsrequired | any[] | |
last_countrequired | integer | null | |
last_counted_atrequired | string | null | |
created_atrequired | string | |
updated_atrequired | string |
Tag
Fields
idrequired | string | |
objectrequired | "tag" | |
namerequired | string | |
colorrequired | "neutral" | "positive" | "warning" | "danger" | "info" | |
created_atrequired | string | |
updated_atrequired | string |
Webhook
A webhook endpoint subscription (URL + events). Not the body we POST to your URL — that is WebhookEvent.
Fields
idrequired | string | |
objectrequired | "webhook" | |
urlrequired | string | The HTTPS URL we POST events to. |
descriptionrequired | string | null | |
eventsrequired | ("email.delivered" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "contact.unsubscribed" | "campaign.finished")[] | Dotted event names this endpoint is subscribed to. |
statusrequired | "enabled" | "disabled_by_user" | "disabled_by_failure" | |
disabled_reasonrequired | string | null | |
consecutive_failuresrequired | integer | |
last_success_atrequired | string | null | |
last_failure_atrequired | string | null | |
secretrequired | string | null | Signing secret. Present only on the create/rotate response; null on every read afterwards. |
created_atrequired | string | |
updated_atrequired | string |
WebhookEvent
The JSON body we POST to your endpoint URL when a subscribed event happens. Includes the contact's email on message and opt-out events so you can match them to your own records. Not the same as the Webhook resource (the endpoint subscription).
Example
{
"id": "evt_01hxyz",
"type": "email.opened",
"occurred_at": "2026-08-03T10:30:00.000Z",
"organization_id": "org_01hxyz",
"data": {
"campaign_id": "cmp_01hxyz",
"contact_id": "con_01hxyz",
"email": "[email protected]",
"message_recipient_id": "mr_01hxyz",
"machine": false
}
}Fields
idrequired | string | Stable source-event id. Delivery is at-least-once — key your idempotency on this. |
typerequired | "email.delivered" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "contact.unsubscribed" | "campaign.finished" | The dotted event name you subscribed to, e.g. email.opened. |
occurred_atrequired | string | When the event happened, not when we delivered it. |
organization_idrequired | string | Your organisation. |
datarequired | object | Message-event fields. For contact.unsubscribed, data is {contact_id, email, campaign_id?}. For campaign.finished, data is {campaign_id, name, sent, failed, total} — no contact fields. |
campaign_idrequired | string | The campaign that sent the message. |
contact_idrequired | string | Our id for the person. |
emailrequired | string | null | The contact's address — match this to a contact in your system without calling the API back. Null only if the contact was deleted between the event and the emit. |
message_recipient_idrequired | string | This specific send of the message. |
machinerequired | boolean | True when an open or click came from a scanner or link unfurler rather than a person. Always present on message events, including when false. Machine hits are excluded from every figure in the app. |
link_url | string | null | Clicks only — the URL that was clicked. |
bounce_type | string | null | Bounces only — permanent, transient, or undetermined. |
bounce_subtype | string | null | Bounces only — provider subtype when available. |
diagnostic_code | string | null | Bounces only — SMTP diagnostic when available. |
Everything here is generated from the schemas the API actually validates against. The machine-readable form is at /api/v1/openapi.json.