Skip to content

Developers

The API, generated from the code that runs it

A cursor-paged REST API over contacts, lists, tags, segments, campaigns and automations.

Base URLhttps://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.

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

get/automationsautomation:view

List automations

Query parameters

cursorstringnext_cursor from the previous page, passed back unmodified.
limitintegerRows per page, 1–100. Defaults to 50.
status("draft" | "active" | "paused" | "archived") | ("draft" | "active" | "paused" | "archived")[]
Returns200 page of Automation in dataErrors: 400, 401, 403, 429, 500

Example 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
}
get/automations/{id}automation:view

Retrieve an automation

Path parameters

idrequiredstringThe automation's id.
Returns200 Automation in dataErrors: 400, 401, 403, 404, 429, 500

Example 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"
  }
}
post/automations/{id}/activateautomation:edit

Turn an automation on, or resume a paused one

Path parameters

idrequiredstringThe automation's id.

Headers

Idempotency-KeystringA 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.
Returns200 Automation in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example 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"
  }
}
post/automations/{id}/enrollautomation:edit

Enrol contacts into an active automation

Path parameters

idrequiredstringThe automation's id.

Headers

Idempotency-KeystringA 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_idsrequiredstring[]
Returns200 Enrollment in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example response

{
  "data": {
    "object": "enrollment",
    "requested": 128,
    "enrolled": 128
  }
}
post/automations/{id}/pauseautomation:edit

Pause an automation, holding everyone inside it

Path parameters

idrequiredstringThe automation's id.

Headers

Idempotency-KeystringA 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.
Returns200 Automation in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example 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

get/campaignscampaign:view

List campaigns

Query parameters

cursorstringnext_cursor from the previous page, passed back unmodified.
limitintegerRows 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")[]
Returns200 page of Campaign in dataErrors: 400, 401, 403, 429, 500

Example 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
}
post/campaignscampaign:edit

Create a campaign

Headers

Idempotency-KeystringA 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

namerequiredstring
Returns201 Campaign in dataErrors: 400, 401, 403, 409, 422, 429, 500

Example 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"
  }
}
get/campaigns/{id}campaign:view

Retrieve a campaign

Path parameters

idrequiredstringThe campaign's id.
Returns200 Campaign in dataErrors: 400, 401, 403, 404, 429, 500

Example 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"
  }
}
patch/campaigns/{id}campaign:edit

Update a draft campaign

Path parameters

idrequiredstringThe campaign's id.

Headers

Idempotency-KeystringA 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

namestring
subjectstring | null
preheaderstring | null
sender_identity_idstring
reply_to"" | string | null
email_document_idstring
audienceobject
includeobject
excludeobject
track_opensboolean
track_clicksboolean
Returns200 Campaign in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example 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"
  }
}
delete/campaigns/{id}campaign:delete

Delete a draft or cancelled campaign

Path parameters

idrequiredstringThe campaign's id.

Headers

Idempotency-KeystringA 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.
Returns204 no bodyErrors: 400, 401, 403, 404, 409, 422, 429, 500
post/campaigns/{id}/cancelcampaign:send

Cancel a scheduled or sending campaign

Path parameters

idrequiredstringThe campaign's id.

Headers

Idempotency-KeystringA 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.
Returns200 Campaign in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example 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"
  }
}
post/campaigns/{id}/schedulecampaign:send

Schedule a campaign, or send it now

Path parameters

idrequiredstringThe campaign's id.

Headers

Idempotency-KeystringA 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_atstringWhen to send, as an ISO 8601 instant. Omit to send now.
Returns200 Campaign in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example 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"
  }
}
get/campaigns/{id}/statsanalytics:view

Retrieve a campaign's engagement totals and rates

Path parameters

idrequiredstringThe campaign's id.
Returns200 CampaignStats in dataErrors: 400, 401, 403, 404, 429, 500

Example 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

get/contactscontact:view

List contacts

Query parameters

cursorstringnext_cursor from the previous page, passed back unmodified.
limitintegerRows per page, 1–100. Defaults to 50.
status("subscribed" | "unsubscribed" | "bounced" | "complained" | "pending") | ("subscribed" | "unsubscribed" | "bounced" | "complained" | "pending")[]
list_idstring | string[]
tag_idstring | string[]
searchstring
Returns200 page of Contact in dataErrors: 400, 401, 403, 429, 500

Example 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
}
post/contactscontact:edit

Create or update a contact

Headers

Idempotency-KeystringA 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

emailrequiredstring
first_namestring | null
last_namestring | null
opt_in_sourcestring | null
status"subscribed" | "unsubscribed" | "bounced" | "complained" | "pending"
Returns200 or 201 Contact in dataErrors: 400, 401, 403, 409, 422, 429, 500

Example 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"
  }
}
get/contacts/{id}contact:view

Retrieve a contact

Path parameters

idrequiredstringThe contact's id.
Returns200 Contact in dataErrors: 400, 401, 403, 404, 429, 500

Example 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"
  }
}
patch/contacts/{id}contact:edit

Update a contact

Path parameters

idrequiredstringThe contact's id.

Headers

Idempotency-KeystringA 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

emailstring
first_namestring | null
last_namestring | null
opt_in_sourcestring | null
status"subscribed" | "unsubscribed" | "bounced" | "complained" | "pending"
Returns200 Contact in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example 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"
  }
}
delete/contacts/{id}contact:delete

Delete a contact

Path parameters

idrequiredstringThe contact's id.

Headers

Idempotency-KeystringA 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.
Returns204 no bodyErrors: 400, 401, 403, 404, 409, 422, 429, 500
get/contacts/{id}/listslist:view

List the lists a contact belongs to

Path parameters

idrequiredstringThe contact's id.

Query parameters

cursorstringnext_cursor from the previous page, passed back unmodified.
limitintegerRows per page, 1–100. Defaults to 50.
Returns200 page of List in dataErrors: 400, 401, 403, 404, 429, 500

Example 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
}
get/contacts/{id}/tagslist:view

List the tags on a contact

Path parameters

idrequiredstringThe contact's id.

Query parameters

cursorstringnext_cursor from the previous page, passed back unmodified.
limitintegerRows per page, 1–100. Defaults to 50.
Returns200 page of Tag in dataErrors: 400, 401, 403, 404, 429, 500

Example response

{
  "data": [
    {
      "id": "cly3k9f2b0000q8n1h4d7g5xz",
      "object": "tag",
      "name": "Weekly digest",
      "color": "neutral",
      "created_at": "2026-07-14T09:31:02.000Z",
      "updated_at": "2026-07-14T09:31:02.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Custom fields

get/custom_fieldscontact:view

List the organisation's custom field definitions

Query parameters

cursorstringnext_cursor from the previous page, passed back unmodified.
limitintegerRows per page, 1–100. Defaults to 50.
Returns200 page of CustomField in dataErrors: 400, 401, 403, 429, 500

Example 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

get/listslist:view

List contact lists

Query parameters

cursorstringnext_cursor from the previous page, passed back unmodified.
limitintegerRows per page, 1–100. Defaults to 50.
namestring
Returns200 page of List in dataErrors: 400, 401, 403, 429, 500

Example 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
}
post/listslist:edit

Create a contact list

Headers

Idempotency-KeystringA 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

namerequiredstring
descriptionstring | null
Returns201 List in dataErrors: 400, 401, 403, 409, 422, 429, 500

Example 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"
  }
}
get/lists/{id}list:view

Retrieve a contact list

Path parameters

idrequiredstringThe list's id.
Returns200 List in dataErrors: 400, 401, 403, 404, 429, 500

Example 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"
  }
}
patch/lists/{id}list:edit

Update a contact list

Path parameters

idrequiredstringThe list's id.

Headers

Idempotency-KeystringA 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

namestring
descriptionstring | null
Returns200 List in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example 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"
  }
}
delete/lists/{id}list:edit

Delete a contact list, keeping its contacts

Path parameters

idrequiredstringThe list's id.

Headers

Idempotency-KeystringA 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.
Returns204 no bodyErrors: 400, 401, 403, 404, 409, 422, 429, 500
get/lists/{id}/contactscontact:view

List the contacts on a list

Path parameters

idrequiredstringThe list's id.

Query parameters

cursorstringnext_cursor from the previous page, passed back unmodified.
limitintegerRows per page, 1–100. Defaults to 50.
Returns200 page of Contact in dataErrors: 400, 401, 403, 404, 429, 500

Example 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
}
post/lists/{id}/contactslist:edit

Add contacts to a list

Path parameters

idrequiredstringThe list's id.

Headers

Idempotency-KeystringA 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_idsrequiredstring[]
Returns200 MembershipChange in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example response

{
  "data": {
    "object": "membership_change",
    "changed": 128
  }
}
delete/lists/{id}/contactslist:edit

Remove contacts from a list

Path parameters

idrequiredstringThe list's id.

Headers

Idempotency-KeystringA 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_idsrequiredstring[]
Returns200 MembershipChange in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example response

{
  "data": {
    "object": "membership_change",
    "changed": 128
  }
}

Orders

get/ordersorder:view

List orders

Query parameters

cursorstringnext_cursor from the previous page, passed back unmodified.
limitintegerRows per page, 1–100. Defaults to 50.
emailstring
status"paid" | "refunded" | "partially_refunded" | "cancelled"
attributed"true" | "false"
Returns200 page of Order in dataErrors: 400, 401, 403, 429, 500

Example 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
}
post/ordersorder:edit

Create or update an order

Headers

Idempotency-KeystringA 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_idrequiredstring
emailrequiredstring
status"paid" | "refunded" | "partially_refunded" | "cancelled"
total_centsrequiredinteger
refunded_centsinteger
currencyrequiredstring
occurred_atrequiredstring
sourcestring | null
Returns200 or 201 Order in dataErrors: 400, 401, 403, 409, 422, 429, 500

Example 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"
  }
}
get/orders/{id}order:view

Retrieve an order

Path parameters

idrequiredstringThe order's id.
Returns200 Order in dataErrors: 400, 401, 403, 404, 429, 500

Example 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

get/segmentssegment:view

List segments

Query parameters

cursorstringnext_cursor from the previous page, passed back unmodified.
limitintegerRows per page, 1–100. Defaults to 50.
namestring
Returns200 page of Segment in dataErrors: 400, 401, 403, 429, 500

Example 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
}
post/segmentssegment:edit

Create a segment

Headers

Idempotency-KeystringA 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

namerequiredstring
descriptionstring | null
rulesrequiredany
Returns201 Segment in dataErrors: 400, 401, 403, 409, 422, 429, 500

Example 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"
  }
}
get/segments/{id}segment:view

Retrieve a segment

Path parameters

idrequiredstringThe segment's id.
Returns200 Segment in dataErrors: 400, 401, 403, 404, 429, 500

Example 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"
  }
}
patch/segments/{id}segment:edit

Update a segment

Path parameters

idrequiredstringThe segment's id.

Headers

Idempotency-KeystringA 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

namestring
descriptionstring | null
rulesany
Returns200 Segment in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example 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"
  }
}
delete/segments/{id}segment:edit

Delete a segment

Path parameters

idrequiredstringThe segment's id.

Headers

Idempotency-KeystringA 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.
Returns204 no bodyErrors: 400, 401, 403, 404, 409, 422, 429, 500
get/segments/{id}/contactscontact:view

List the contacts a segment selects

Path parameters

idrequiredstringThe segment's id.

Query parameters

cursorstringnext_cursor from the previous page, passed back unmodified.
limitintegerRows per page, 1–100. Defaults to 50.
Returns200 page of Contact in dataErrors: 400, 401, 403, 404, 429, 500

Example 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
}

Tags

get/tagslist:view

List tags

Query parameters

cursorstringnext_cursor from the previous page, passed back unmodified.
limitintegerRows per page, 1–100. Defaults to 50.
namestring
Returns200 page of Tag in dataErrors: 400, 401, 403, 429, 500

Example response

{
  "data": [
    {
      "id": "cly3k9f2b0000q8n1h4d7g5xz",
      "object": "tag",
      "name": "Weekly digest",
      "color": "neutral",
      "created_at": "2026-07-14T09:31:02.000Z",
      "updated_at": "2026-07-14T09:31:02.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
post/tagslist:edit

Create a tag

Headers

Idempotency-KeystringA 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

namerequiredstring
color"neutral" | "positive" | "warning" | "danger" | "info"
Returns201 Tag in dataErrors: 400, 401, 403, 409, 422, 429, 500

Example response

{
  "data": {
    "id": "cly3k9f2b0000q8n1h4d7g5xz",
    "object": "tag",
    "name": "Weekly digest",
    "color": "neutral",
    "created_at": "2026-07-14T09:31:02.000Z",
    "updated_at": "2026-07-14T09:31:02.000Z"
  }
}
get/tags/{id}list:view

Retrieve a tag

Path parameters

idrequiredstringThe tag's id.
Returns200 Tag in dataErrors: 400, 401, 403, 404, 429, 500

Example response

{
  "data": {
    "id": "cly3k9f2b0000q8n1h4d7g5xz",
    "object": "tag",
    "name": "Weekly digest",
    "color": "neutral",
    "created_at": "2026-07-14T09:31:02.000Z",
    "updated_at": "2026-07-14T09:31:02.000Z"
  }
}
patch/tags/{id}list:edit

Update a tag

Path parameters

idrequiredstringThe tag's id.

Headers

Idempotency-KeystringA 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

namestring
color"neutral" | "positive" | "warning" | "danger" | "info"
Returns200 Tag in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example response

{
  "data": {
    "id": "cly3k9f2b0000q8n1h4d7g5xz",
    "object": "tag",
    "name": "Weekly digest",
    "color": "neutral",
    "created_at": "2026-07-14T09:31:02.000Z",
    "updated_at": "2026-07-14T09:31:02.000Z"
  }
}
delete/tags/{id}list:edit

Delete a tag, keeping its contacts

Path parameters

idrequiredstringThe tag's id.

Headers

Idempotency-KeystringA 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.
Returns204 no bodyErrors: 400, 401, 403, 404, 409, 422, 429, 500
post/tags/{id}/contactslist:edit

Apply a tag to contacts

Path parameters

idrequiredstringThe tag's id.

Headers

Idempotency-KeystringA 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_idsrequiredstring[]
Returns200 MembershipChange in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example response

{
  "data": {
    "object": "membership_change",
    "changed": 128
  }
}
delete/tags/{id}/contactslist:edit

Remove a tag from contacts

Path parameters

idrequiredstringThe tag's id.

Headers

Idempotency-KeystringA 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_idsrequiredstring[]
Returns200 MembershipChange in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example response

{
  "data": {
    "object": "membership_change",
    "changed": 128
  }
}

Webhooks

get/webhookswebhook:manage

List webhook endpoints

Query parameters

cursorstringnext_cursor from the previous page, passed back unmodified.
limitintegerRows per page, 1–100. Defaults to 50.
Returns200 page of Webhook in dataErrors: 400, 401, 403, 429, 500

Example 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
}
post/webhookswebhook:manage

Create a webhook endpoint

Headers

Idempotency-KeystringA 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

urlrequiredstring
eventsrequired("email.delivered" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "contact.unsubscribed" | "campaign.finished")[]
descriptionstring
Returns201 Webhook in dataErrors: 400, 401, 403, 409, 422, 429, 500

Example 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

idrequiredstringStable 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_atrequiredstringWhen the event happened, not when we delivered it.
organization_idrequiredstringYour organisation.
datarequiredobjectMessage-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_idrequiredstringThe campaign that sent the message.
contact_idrequiredstringOur id for the person.
emailrequiredstring | nullThe 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_idrequiredstringThis specific send of the message.
machinerequiredbooleanTrue 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_urlstring | nullClicks only — the URL that was clicked.
bounce_typestring | nullBounces only — permanent, transient, or undetermined.
bounce_subtypestring | nullBounces only — provider subtype when available.
diagnostic_codestring | nullBounces only — SMTP diagnostic when available.
get/webhooks/{id}webhook:manage

Retrieve a webhook endpoint

Path parameters

idrequiredstringThe webhook's id.
Returns200 Webhook in dataErrors: 400, 401, 403, 404, 429, 500

Example 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"
  }
}
patch/webhooks/{id}webhook:manage

Update a webhook endpoint

Path parameters

idrequiredstringThe webhook's id.

Headers

Idempotency-KeystringA 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

urlstring
events("email.delivered" | "email.bounced" | "email.complained" | "email.opened" | "email.clicked" | "contact.unsubscribed" | "campaign.finished")[]
descriptionstring | null
status"enabled" | "disabled_by_user"
Returns200 Webhook in dataErrors: 400, 401, 403, 404, 409, 422, 429, 500

Example 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"
  }
}
delete/webhooks/{id}webhook:manage

Delete a webhook endpoint

Path parameters

idrequiredstringThe webhook's id.

Headers

Idempotency-KeystringA 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.
Returns204 no bodyErrors: 400, 401, 403, 404, 409, 422, 429, 500

Object 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

idrequiredstring
objectrequired"automation"
namerequiredstring
statusrequired"draft" | "active" | "paused" | "archived"
versionrequiredinteger
allow_reentryrequiredboolean
definitionrequiredobjectThe flow, in the grammar automationDefinitionSchema defines. Read-only in v1.
versionrequiredinteger
entryNodeIdrequiredstring | null
nodesrequiredobject
activated_atrequiredstring | null
created_atrequiredstring
updated_atrequiredstring

Campaign

Fields

idrequiredstring
objectrequired"campaign"
namerequiredstring
statusrequired"draft" | "scheduled" | "dispatching" | "sending" | "testing" | "paused" | "sent" | "cancelled" | "failed"
subjectrequiredstring | null
preheaderrequiredstring | null
sender_identity_idrequiredstring | null
from_namerequiredstring | null
from_emailrequiredstring | null
reply_torequiredstring | null
email_document_idrequiredstring | null
audiencerequiredobject
includerequiredobject
excluderequiredobject
track_opensrequiredboolean
track_clicksrequiredboolean
scheduled_atrequiredstring | null
send_started_atrequiredstring | null
send_completed_atrequiredstring | null
recipient_countrequiredinteger
created_atrequiredstring
updated_atrequiredstring

CampaignStats

Fields

objectrequired"campaign_stats"
campaign_idrequiredstring
sentrequiredinteger
deliveredrequiredinteger
openedrequiredinteger
unique_openedrequiredinteger
clickedrequiredinteger
unique_clickedrequiredinteger
bouncedrequiredinteger
soft_bouncedrequiredinteger
complainedrequiredinteger
unsubscribedrequiredinteger
failedrequiredinteger
open_raterequirednumber | null
click_raterequirednumber | null
click_to_open_raterequirednumber | null
bounce_raterequirednumber | null
hard_bounce_raterequirednumber | null
complaint_raterequirednumber | null
unsubscribe_raterequirednumber | null
updated_atrequiredstring | null

Contact

Fields

idrequiredstring
objectrequired"contact"
emailrequiredstring
first_namerequiredstring | null
last_namerequiredstring | null
statusrequired"subscribed" | "unsubscribed" | "bounced" | "complained" | "pending"
custom_fieldsrequiredobject
opt_in_sourcerequiredstring | null
opt_in_atrequiredstring | null
last_engaged_atrequiredstring | null
created_atrequiredstring
updated_atrequiredstring

CustomField

Fields

idrequiredstring
objectrequired"custom_field"
keyrequiredstring
labelrequiredstring
typerequired"text" | "number" | "date" | "boolean"
created_atrequiredstring
updated_atrequiredstring

Enrollment

Fields

objectrequired"enrollment"
requestedrequiredinteger
enrolledrequiredinteger

Error

Every 4xx and 5xx response has this shape, whatever went wrong.

Fields

errorrequiredobject
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.
messagerequiredstring
paramstringThe offending field, as a dotted path. Present when one field is at fault.
request_idrequiredstringAlso returned in X-Request-Id. Quote it in a support message and we can find exactly this request.

List

Fields

idrequiredstring
objectrequired"list"
namerequiredstring
descriptionrequiredstring | null
created_atrequiredstring
updated_atrequiredstring

MembershipChange

Fields

objectrequired"membership_change"
changedrequiredinteger

Order

Fields

idrequiredstring
objectrequired"order"
external_idrequiredstring
emailrequiredstring
contact_idrequiredstring | null
statusrequired"paid" | "refunded" | "partially_refunded" | "cancelled"
total_centsrequiredinteger
refunded_centsrequiredinteger
currencyrequiredstring
occurred_atrequiredstring
sourcerequiredstring | null
attributed_campaign_idrequiredstring | null
attributed_automation_idrequiredstring | null
attributed_node_idrequiredstring | null
attribution_rulerequiredstring | null
attributed_atrequiredstring | null
created_atrequiredstring
updated_atrequiredstring

Segment

Fields

idrequiredstring
objectrequired"segment"
namerequiredstring
descriptionrequiredstring | null
rulesrequiredobjectThe saved rule tree, in the grammar segmentRulesSchema defines. Pass it back unchanged to leave it alone.
versionrequiredinteger
matchrequired"all" | "any"
groupsrequiredany[]
last_countrequiredinteger | null
last_counted_atrequiredstring | null
created_atrequiredstring
updated_atrequiredstring

Tag

Fields

idrequiredstring
objectrequired"tag"
namerequiredstring
colorrequired"neutral" | "positive" | "warning" | "danger" | "info"
created_atrequiredstring
updated_atrequiredstring

Webhook

A webhook endpoint subscription (URL + events). Not the body we POST to your URL — that is WebhookEvent.

Fields

idrequiredstring
objectrequired"webhook"
urlrequiredstringThe HTTPS URL we POST events to.
descriptionrequiredstring | 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_reasonrequiredstring | null
consecutive_failuresrequiredinteger
last_success_atrequiredstring | null
last_failure_atrequiredstring | null
secretrequiredstring | nullSigning secret. Present only on the create/rotate response; null on every read afterwards.
created_atrequiredstring
updated_atrequiredstring

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

idrequiredstringStable 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_atrequiredstringWhen the event happened, not when we delivered it.
organization_idrequiredstringYour organisation.
datarequiredobjectMessage-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_idrequiredstringThe campaign that sent the message.
contact_idrequiredstringOur id for the person.
emailrequiredstring | nullThe 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_idrequiredstringThis specific send of the message.
machinerequiredbooleanTrue 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_urlstring | nullClicks only — the URL that was clicked.
bounce_typestring | nullBounces only — permanent, transient, or undetermined.
bounce_subtypestring | nullBounces only — provider subtype when available.
diagnostic_codestring | nullBounces 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.