Platform

Contacts, webhooks, keys, and the rest. Every call is on this page.

Contacts

Manage the people you email. Create and update contacts one at a time or import thousands in the background, with standard and custom field values that templates can reference as variables.

List contacts

GET/api/v1/contacts

GET /contacts
curl -X GET 'https://www.unitpost.com/api/v1/contacts' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "contact",
      "id": "id_123",
      "email": "customer@example.com",
      "first_name": "string",
      "last_name": "string",
      "unsubscribed": false,
      "unsubscribed_at": "2026-01-01T00:00:00.000Z",
      "unsubscribe_reason": "USER",
      "custom_fields": {},
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of the contacts in your workspace, newest-first and cursor-paginated. Each row includes the contact's core fields and any custom field values. Use a contact's id (or email) with Retrieve a contact for the full record, add contacts with Create a contact, or bring a list in bulk with Start an async contact import. Requires the contacts:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields11 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
emailrequiredobject—
first_namerequiredobject—
last_namerequiredobject—
unsubscribedrequiredboolean—
unsubscribed_atrequiredobject—
unsubscribe_reasonrequiredobject—
custom_fieldsrequiredobject—
created_atrequiredstring—
updated_atrequiredstring—
200401422

Create a contact

POST/api/v1/contacts

POST /contacts
curl -X POST 'https://www.unitpost.com/api/v1/contacts' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "customer@example.com",
    "first_name": "Ada",
    "last_name": "Lovelace"
  }'
Response · 201
{
  "object": "contact",
  "id": "id_123",
  "email": "customer@example.com",
  "first_name": "string",
  "last_name": "string",
  "unsubscribed": false,
  "unsubscribed_at": "2026-01-01T00:00:00.000Z",
  "unsubscribe_reason": "USER",
  "custom_fields": {},
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Add a single contact to your workspace. Set an email plus any standard or custom field values; custom fields must already exist (define them with Create a custom contact field). Emails are unique per workspace, so creating one that already exists returns 409 — use Update a contact to change an existing contact, or Start an async contact import to add many at once. Requires the contacts:write capability.

Request body5 fields
FieldTypeDescription
emailrequiredstring—
first_namestring—
last_namestring—
unsubscribedboolean—
custom_fieldsobject—
Response · 201 fields11 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
emailrequiredobject—
first_namerequiredobject—
last_namerequiredobject—
unsubscribedrequiredboolean—
unsubscribed_atrequiredobject—
unsubscribe_reasonrequiredobject—
custom_fieldsrequiredobject—
created_atrequiredstring—
updated_atrequiredstring—
201401409422

Retrieve a contact

GET/api/v1/contacts/{id}

GET /contacts/{id}
curl -X GET 'https://www.unitpost.com/api/v1/contacts/con_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "contact",
  "id": "id_123",
  "email": "customer@example.com",
  "first_name": "string",
  "last_name": "string",
  "unsubscribed": false,
  "unsubscribed_at": "2026-01-01T00:00:00.000Z",
  "unsubscribe_reason": "USER",
  "custom_fields": {},
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a single contact and all of its field values. The {id} path segment accepts either the contact's id or its email address, so you can look a contact up without storing our id. Change it with Update a contact, or see which subscription topics it's opted into with List a contact's topic subscriptions. Requires the contacts:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
Response · 200 fields11 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
emailrequiredobject—
first_namerequiredobject—
last_namerequiredobject—
unsubscribedrequiredboolean—
unsubscribed_atrequiredobject—
unsubscribe_reasonrequiredobject—
custom_fieldsrequiredobject—
created_atrequiredstring—
updated_atrequiredstring—
200401404

Update a contact

PATCH/api/v1/contacts/{id}

PATCH /contacts/{id}
curl -X PATCH 'https://www.unitpost.com/api/v1/contacts/con_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{
    "unsubscribed": true
  }'
Response · 200
{
  "object": "contact",
  "id": "id_123",
  "email": "customer@example.com",
  "first_name": "string",
  "last_name": "string",
  "unsubscribed": false,
  "unsubscribed_at": "2026-01-01T00:00:00.000Z",
  "unsubscribe_reason": "USER",
  "custom_fields": {},
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Update a contact's standard or custom field values. Only the fields you send are changed; omitted fields are left as-is. The {id} accepts a contact id or email. To manage a contact's subscription preferences instead, use Set a contact's topic preference. Requires the contacts:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
Request body4 fields
FieldTypeDescription
first_nameobject—
last_nameobject—
unsubscribedboolean—
custom_fieldsobject—
Response · 200 fields11 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
emailrequiredobject—
first_namerequiredobject—
last_namerequiredobject—
unsubscribedrequiredboolean—
unsubscribed_atrequiredobject—
unsubscribe_reasonrequiredobject—
custom_fieldsrequiredobject—
created_atrequiredstring—
updated_atrequiredstring—
200401404422

Delete a contact

DELETE/api/v1/contacts/{id}

DELETE /contacts/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/contacts/con_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'

Permanently delete a contact and its field values. This also removes the contact from every segment it belonged to. To stop mailing someone without deleting them, unsubscribe them from a topic with Set a contact's topic preference or add them to your suppression list instead. Requires the contacts:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
204401404

List contact imports

GET/api/v1/contacts/imports

GET /contacts/imports
curl -X GET 'https://www.unitpost.com/api/v1/contacts/imports' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "contact_import",
      "id": "id_123",
      "status": "pending",
      "total": 1,
      "imported": 0,
      "skipped": 0,
      "failed": 0,
      "invalid": [
        {
          "row": 0,
          "reason": "string"
        }
      ],
      "error": "string",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of your bulk contact-import jobs, newest-first and cursor-paginated. Each row is a reference to one import and its current progress. Start a new one with Start an async contact import, or drill into a single job with Retrieve a contact import. Requires the contacts:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields11 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
statusrequiredenumpending | processing | completed | failed—
totalrequirednumber—
importedrequirednumber—
skippedrequirednumber—
failedrequirednumber—
invalidrequiredobject[]—
errorrequiredobject—
created_atrequiredstring—
updated_atrequiredstring—
200401422

Start an async contact import

POST/api/v1/contacts/imports

POST /contacts/imports
curl -X POST 'https://www.unitpost.com/api/v1/contacts/imports' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{ }'
Response · 202
{
  "object": "contact_import",
  "id": "id_123",
  "status": "pending",
  "total": 1,
  "imported": 0,
  "skipped": 0,
  "failed": 0,
  "invalid": [
    {
      "row": 0,
      "reason": "string"
    }
  ],
  "error": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Import a batch of contacts (up to 5,000 per request) in the background. The call returns immediately with an import job you poll for progress and per-row results — new contacts are created and existing ones (matched by email) are updated. Track it with Retrieve a contact import, or see all your imports via List contact imports. To add a single contact synchronously, use Create a contact. Requires the contacts:write capability.

Request body1 field
FieldTypeDescription
contactsrequiredobject[]—
Response · 202 fields11 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
statusrequiredenumpending | processing | completed | failed—
totalrequirednumber—
importedrequirednumber—
skippedrequirednumber—
failedrequirednumber—
invalidrequiredobject[]—
errorrequiredobject—
created_atrequiredstring—
updated_atrequiredstring—
202401422

Retrieve a contact import

GET/api/v1/contacts/imports/{id}

GET /contacts/imports/{id}
curl -X GET 'https://www.unitpost.com/api/v1/contacts/imports/imp_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "contact_import",
  "id": "id_123",
  "status": "pending",
  "total": 1,
  "imported": 0,
  "skipped": 0,
  "failed": 0,
  "invalid": [
    {
      "row": 0,
      "reason": "string"
    }
  ],
  "error": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a bulk import job by id to check its status and per-row results — how many contacts were created, updated, or skipped, and why any rows failed. Poll this while an import runs. Start one with Start an async contact import. Requires the contacts:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
Response · 200 fields11 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
statusrequiredenumpending | processing | completed | failed—
totalrequirednumber—
importedrequirednumber—
skippedrequirednumber—
failedrequirednumber—
invalidrequiredobject[]—
errorrequiredobject—
created_atrequiredstring—
updated_atrequiredstring—
200401404

Contact fields

Define the typed custom attributes (text, number, date, …) your contacts carry — usable in templates as per-recipient variables.

List custom contact fields

GET/api/v1/contact-fields

GET /contact-fields
curl -X GET 'https://www.unitpost.com/api/v1/contact-fields' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "contact_field",
      "id": "id_123",
      "key": "string",
      "label": "string",
      "type": "text",
      "default_value": "string",
      "position": 0,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of the custom contact fields defined in your workspace, cursor-paginated. These are the typed attributes (text, number, date, boolean, and so on) you can set on any contact and reference as template variables. Define a new one with Create a custom contact field. Requires the contacts:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
keyrequiredstring—
labelrequiredstring—
typerequiredenumtext | number | boolean | date—
default_valuerequiredobject—
positionrequirednumber—
created_atrequiredstring—
updated_atrequiredstring—
200401422

Create a custom contact field

POST/api/v1/contact-fields

POST /contact-fields
curl -X POST 'https://www.unitpost.com/api/v1/contact-fields' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{
    "key": "plan",
    "label": "Plan",
    "type": "text"
  }'
Response · 201
{
  "object": "contact_field",
  "id": "id_123",
  "key": "string",
  "label": "string",
  "type": "text",
  "default_value": "string",
  "position": 0,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Define a new typed custom field that can then be set on any contact and used as a template variable (e.g. plan, signup_date). The key and type are fixed once created — rename the key later with Rename a custom field's key, or change the label with Update a custom contact field. See existing fields via List custom contact fields. Requires the contacts:write capability.

Request body4 fields
FieldTypeDescription
keyrequiredstring—
labelrequiredstring—
typeenumtext | number | boolean | date—
default_valueobject—
Response · 201 fields9 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
keyrequiredstring—
labelrequiredstring—
typerequiredenumtext | number | boolean | date—
default_valuerequiredobject—
positionrequirednumber—
created_atrequiredstring—
updated_atrequiredstring—
201401409422

Retrieve a custom contact field

GET/api/v1/contact-fields/{id}

GET /contact-fields/{id}
curl -X GET 'https://www.unitpost.com/api/v1/contact-fields/cf_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "contact_field",
  "id": "id_123",
  "key": "string",
  "label": "string",
  "type": "text",
  "default_value": "string",
  "position": 0,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a single custom contact field by id, including its key, type, and label. Update its label with Update a custom contact field, or rename the key with Rename a custom field's key. Requires the contacts:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
keyrequiredstring—
labelrequiredstring—
typerequiredenumtext | number | boolean | date—
default_valuerequiredobject—
positionrequirednumber—
created_atrequiredstring—
updated_atrequiredstring—
200401404

Update a custom contact field

PATCH/api/v1/contact-fields/{id}

PATCH /contact-fields/{id}
curl -X PATCH 'https://www.unitpost.com/api/v1/contact-fields/cf_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{ }'
Response · 200
{
  "object": "contact_field",
  "id": "id_123",
  "key": "string",
  "label": "string",
  "type": "text",
  "default_value": "string",
  "position": 0,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Update a custom field's editable metadata, such as its display label. The key and type are immutable here — to change the key (and migrate every contact's stored value) use Rename a custom field's key instead. Requires the contacts:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
Request body4 fields
FieldTypeDescription
labelstring—
typeenumtext | number | boolean | date—
positioninteger—
default_valueobject—
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
keyrequiredstring—
labelrequiredstring—
typerequiredenumtext | number | boolean | date—
default_valuerequiredobject—
positionrequirednumber—
created_atrequiredstring—
updated_atrequiredstring—
200401404422

Delete a custom contact field

DELETE/api/v1/contact-fields/{id}

DELETE /contact-fields/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/contact-fields/cf_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'

Permanently delete a custom field definition and strip its stored value from every contact. Any template variable that referenced it will resolve to empty. This can't be undone — recreate the field with Create a custom contact field if you need it back. Requires the contacts:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
204401404

Rename a custom field's key

POST/api/v1/contact-fields/{id}/rename

POST /contact-fields/{id}/rename
curl -X POST 'https://www.unitpost.com/api/v1/contact-fields/cf_123/rename' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{
    "key": "subscription_plan"
  }'
Response · 200
{
  "object": "contact_field",
  "id": "id_123",
  "key": "string",
  "label": "string",
  "type": "text",
  "default_value": "string",
  "position": 0,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Change a custom field's key, migrating the stored value on every contact so no data is lost. Because the key is how templates and the API reference the field, update any template variables that used the old key. To change only the display label, use Update a custom contact field. Requires the contacts:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
Request body1 field
FieldTypeDescription
keyrequiredstring—
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
keyrequiredstring—
labelrequiredstring—
typerequiredenumtext | number | boolean | date—
default_valuerequiredobject—
positionrequirednumber—
created_atrequiredstring—
updated_atrequiredstring—
200401404409422

Segments

Named groups of contacts used to target campaigns. Segments decide who a campaign goes to; topics (below) decide what recipients can opt out of.

List segments

GET/api/v1/segments

GET /segments
curl -X GET 'https://www.unitpost.com/api/v1/segments' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "segment",
      "id": "id_123",
      "name": "Example",
      "description": "string",
      "type": "static",
      "filter": "string",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of the segments in your workspace, cursor-paginated. A segment is a saved group of contacts you can target when sending a campaign. Create one with Create a segment, or list a segment's members with List segment members. Requires the segments:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields8 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
namerequiredstring—
descriptionrequiredobject—
typerequiredenumstatic | dynamic—
filterrequiredobject—
created_atrequiredstring—
updated_atrequiredstring—
200401422

Create a segment

POST/api/v1/segments

POST /segments
curl -X POST 'https://www.unitpost.com/api/v1/segments' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Active customers"
  }'
Response · 201
{
  "object": "segment",
  "id": "id_123",
  "name": "Example",
  "description": "string",
  "type": "static",
  "filter": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Create a segment — a named group of contacts you can target when sending a campaign. Once created, add contacts with Add a member and target it from Create a campaign. To let recipients opt out of a category of mail instead, see Create a topic. Requires the segments:write capability.

Request body4 fields
FieldTypeDescription
namerequiredstring—
descriptionstring—
typeenumstatic | dynamic—
filterobject—
Response · 201 fields8 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
namerequiredstring—
descriptionrequiredobject—
typerequiredenumstatic | dynamic—
filterrequiredobject—
created_atrequiredstring—
updated_atrequiredstring—
201401422

Retrieve a segment

GET/api/v1/segments/{id}

GET /segments/{id}
curl -X GET 'https://www.unitpost.com/api/v1/segments/seg_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "segment",
  "id": "id_123",
  "name": "Example",
  "description": "string",
  "type": "static",
  "filter": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a single segment by id, including its name and member count. To page through the contacts inside it, use List segment members. Requires the segments:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
Response · 200 fields8 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
namerequiredstring—
descriptionrequiredobject—
typerequiredenumstatic | dynamic—
filterrequiredobject—
created_atrequiredstring—
updated_atrequiredstring—
200401404

Update a segment

PATCH/api/v1/segments/{id}

PATCH /segments/{id}
curl -X PATCH 'https://www.unitpost.com/api/v1/segments/seg_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{ }'
Response · 200
{
  "object": "segment",
  "id": "id_123",
  "name": "Example",
  "description": "string",
  "type": "static",
  "filter": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Update a segment's editable metadata, such as its name. To change who's in the segment, add or remove members with Add a member and Remove a member. Requires the segments:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
Request body3 fields
FieldTypeDescription
namestring—
descriptionobject—
filterobject—
Response · 200 fields8 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
namerequiredstring—
descriptionrequiredobject—
typerequiredenumstatic | dynamic—
filterrequiredobject—
created_atrequiredstring—
updated_atrequiredstring—
200401404422

Delete a segment

DELETE/api/v1/segments/{id}

DELETE /segments/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/segments/seg_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'

Delete a segment. The contacts themselves are untouched — only the grouping is removed. This is blocked with 409 while an active campaign still targets the segment; cancel or finish that campaign first (see List campaigns). Requires the segments:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
204401404409

List segment members

GET/api/v1/segments/{id}/contacts

GET /segments/{id}/contacts
curl -X GET 'https://www.unitpost.com/api/v1/segments/seg_123/contacts' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "segment_member",
      "contact_id": "contact_123",
      "subscribed": false,
      "unsubscribed_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve the contacts in a segment, cursor-paginated. Add a contact with Add a member or take one out with Remove a member. Requires the segments:read capability.

Parameters4 fields
FieldTypeDescription
idrequiredstring · path—
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields4 fields
FieldTypeDescription
objectrequiredstring—
contact_idrequiredstring—
subscribedrequiredboolean—
unsubscribed_atrequiredobject—
200401404422

Add a member

POST/api/v1/segments/{id}/contacts

POST /segments/{id}/contacts
curl -X POST 'https://www.unitpost.com/api/v1/segments/seg_123/contacts' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{
    "contact": "customer@example.com"
  }'
Response · 201
{
  "object": "segment_member",
  "contact_id": "contact_123",
  "subscribed": false,
  "unsubscribed_at": "2026-01-01T00:00:00.000Z"
}

Add a contact to a segment, referenced by contact id or email. The operation is idempotent — adding a contact that's already a member is a no-op and still succeeds. See the current members with List segment members. Requires the segments:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
Request body2 fields
FieldTypeDescription
contactrequiredstring—
subscribedboolean—
Response · 201 fields4 fields
FieldTypeDescription
objectrequiredstring—
contact_idrequiredstring—
subscribedrequiredboolean—
unsubscribed_atrequiredobject—
201401404422

Remove a member

DELETE/api/v1/segments/{id}/contacts/{contact}

DELETE /segments/{id}/contacts/{contact}
curl -X DELETE 'https://www.unitpost.com/api/v1/segments/seg_123/contacts/con_456' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'

Remove a contact from a segment, referenced by contact id or email. The contact itself is not deleted — only its membership. Add it back any time with Add a member. Requires the segments:write capability.

Parameters2 fields
FieldTypeDescription
idrequiredstring · path—
contactrequiredstring · pathA contact id or email.
204401404

Webhooks

Register HTTPS endpoints that receive signed event deliveries (delivered, bounced, opened, clicked, …). The signing secret is returned once on create; verify the signature on every delivery.

List webhook endpoints

GET/api/v1/webhooks

GET /webhooks
curl -X GET 'https://www.unitpost.com/api/v1/webhooks' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "webhook",
      "id": "id_123",
      "name": "Example",
      "url": "https://example.com",
      "events": [
        "string"
      ],
      "status": "enabled",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of your webhook endpoints, cursor-paginated, with each endpoint's URL, subscribed events, and status. Signing secrets are never returned here — they're shown exactly once by Create a webhook endpoint. Check an endpoint's wiring end-to-end with Send a test event. Requires the webhooks:read capability.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields8 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
namerequiredstring—
urlrequiredstring—
eventsrequiredstring[]—
statusrequiredenumenabled | disabled | suspended—
created_atrequiredstring—
updated_atrequiredstring—
200401403422

Create a webhook endpoint

POST/api/v1/webhooks

POST /webhooks
curl -X POST 'https://www.unitpost.com/api/v1/webhooks' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://example.com/webhooks/unitpost",
    "events": [
      "email.delivered",
      "email.bounced"
    ]
  }'
Response · 201
{
  "object": "webhook",
  "id": "id_123",
  "name": "Example",
  "url": "https://example.com",
  "events": [
    "string"
  ],
  "status": "enabled",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "signing_secret": "string"
}

Register an https URL to receive event notifications (deliveries, bounces, opens, clicks, and more) and choose which events it subscribes to. The response includes the signing_secret exactly once — store it now, as it can never be retrieved again; use it to verify each delivery's signature. Confirm the endpoint works with Send a test event, and adjust its URL or events later with Update a webhook endpoint. Requires the webhooks:manage capability.

Request body3 fields
FieldTypeDescription
urlrequiredstring—
eventsrequiredstring[]—
namestring—
Response · 201 fields9 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
namerequiredstring—
urlrequiredstring—
eventsrequiredstring[]—
statusrequiredenumenabled | disabled | suspended—
created_atrequiredstring—
updated_atrequiredstring—
signing_secretrequiredstringThe HMAC signing secret. Shown exactly once — store it now; it can never be retrieved again. Use it to verify the signature on every delivery to this endpoint.
201401403409422

Retrieve a webhook endpoint

GET/api/v1/webhooks/{id}

GET /webhooks/{id}
curl -X GET 'https://www.unitpost.com/api/v1/webhooks/wh_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "webhook",
  "id": "id_123",
  "name": "Example",
  "url": "https://example.com",
  "events": [
    "string"
  ],
  "status": "enabled",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve a single webhook endpoint by id — its URL, subscribed events, and status. The signing secret is never included; it's returned exactly once when the endpoint is created. Requires the webhooks:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
Response · 200 fields8 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
namerequiredstring—
urlrequiredstring—
eventsrequiredstring[]—
statusrequiredenumenabled | disabled | suspended—
created_atrequiredstring—
updated_atrequiredstring—
200401403404

Update a webhook endpoint

PATCH/api/v1/webhooks/{id}

PATCH /webhooks/{id}
curl -X PATCH 'https://www.unitpost.com/api/v1/webhooks/wh_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{
    "events": [
      "email.delivered"
    ]
  }'
Response · 200
{
  "object": "webhook",
  "id": "id_123",
  "name": "Example",
  "url": "https://example.com",
  "events": [
    "string"
  ],
  "status": "enabled",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Update a webhook endpoint's url, subscribed events, name, and/or status (use status to pause and re-enable deliveries without deleting the endpoint). The signing secret is never changed by an update. Verify your changes with Send a test event. Requires the webhooks:manage capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
Request body4 fields
FieldTypeDescription
urlstring—
eventsstring[]—
namestring—
statusenumenabled | disabled—
Response · 200 fields8 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
namerequiredstring—
urlrequiredstring—
eventsrequiredstring[]—
statusrequiredenumenabled | disabled | suspended—
created_atrequiredstring—
updated_atrequiredstring—
200401403404409422

Delete a webhook endpoint

DELETE/api/v1/webhooks/{id}

DELETE /webhooks/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/webhooks/wh_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'

Permanently delete a webhook endpoint and its delivery log. Deliveries stop immediately. The operation is idempotent — deleting an already-deleted endpoint still returns 204. To stop deliveries temporarily instead, set status via Update a webhook endpoint. Requires the webhooks:manage capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
204401403

Send a test event

POST/api/v1/webhooks/{id}/test

POST /webhooks/{id}/test
curl -X POST 'https://www.unitpost.com/api/v1/webhooks/wh_123/test' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "webhook_test",
  "delivered": false,
  "response_status": 0,
  "event_type": "string",
  "error": "string"
}

Fire a sample event at the endpoint synchronously and return the receiver's response so you can debug your handler end-to-end. The probe is signed exactly like a real delivery (verify it the same way) but is not persisted to the delivery log. Configure the endpoint itself with Update a webhook endpoint. Requires the webhooks:manage capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
Response · 200 fields5 fields
FieldTypeDescription
objectrequiredenumwebhook_test—
deliveredrequiredbooleanWhether the endpoint accepted the probe (2xx).
response_statusrequiredintegerThe HTTP status the endpoint returned, or null on a transport error.
event_typerequiredstringThe sample event type that was sent.
errorrequiredstringA transport/error message when delivery failed, else null.
200401403404

API keys

Create and revoke the API keys your integrations authenticate with. These endpoints are session-gated (dashboard cookie + apikeys:manage) and NOT usable with a Bearer API key.

List API keys

GET/api/v1/api-keys

GET /api-keys
curl -X GET 'https://www.unitpost.com/api/v1/api-keys' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "api_key",
      "id": "id_123",
      "name": "Example",
      "prefix": "string",
      "scope": "full",
      "capabilities": [
        "string"
      ],
      "last_used_at": "2026-01-01T00:00:00.000Z",
      "revoked_at": "2026-01-01T00:00:00.000Z",
      "created_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of your workspace's API keys — the key prefix and metadata only, never the secret. Create a new key with Create an API key or revoke one with Revoke an API key. This endpoint is session-gated: it requires a dashboard session and cannot be called with a Bearer API key.

Parameters3 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
namerequiredstring—
prefixrequiredstring—
scoperequiredenumfull | sending | read_only—
capabilitiesrequiredstring[]—
last_used_atrequiredobject—
revoked_atrequiredobject—
created_atrequiredstring—
200401403422

Create an API key

POST/api/v1/api-keys

POST /api-keys
curl -X POST 'https://www.unitpost.com/api/v1/api-keys' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Production server",
    "capabilities": [
      "emails:send"
    ]
  }'
Response · 201
{
  "object": "api_key",
  "id": "id_123",
  "name": "Example",
  "prefix": "string",
  "scope": "full",
  "capabilities": [
    "string"
  ],
  "last_used_at": "2026-01-01T00:00:00.000Z",
  "revoked_at": "2026-01-01T00:00:00.000Z",
  "created_at": "2026-01-01T00:00:00.000Z",
  "key": "string"
}

Create an API key scoped to the capabilities you choose. The response includes the plaintext key exactly once — store it securely now, because it can never be retrieved again (only revoked). This endpoint is session-gated: it requires a dashboard session and cannot be called with a Bearer API key.

Request body3 fields
FieldTypeDescription
namerequiredstring—
scopeenumfull | sending | read_only—
capabilitiesenum[]—
Response · 201 fields10 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
namerequiredstring—
prefixrequiredstring—
scoperequiredenumfull | sending | read_only—
capabilitiesrequiredstring[]—
last_used_atrequiredobject—
revoked_atrequiredobject—
created_atrequiredstring—
keyrequiredstringThe plaintext secret. Shown exactly once — store it now; it can never be retrieved again.
201401403422

Revoke an API key

DELETE/api/v1/api-keys/{id}

DELETE /api-keys/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/api-keys/key_123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'

Permanently revoke an API key. Requests made with it start failing with 401 immediately, and revocation cannot be undone — create a replacement first with Create an API key if you're rotating. This endpoint is session-gated: it requires a dashboard session and cannot be called with a Bearer API key.

Parameters1 field
FieldTypeDescription
idrequiredstring · path—
204401403404

Suppressions

Address-level send blocks — the hard "never email this address" list. Combines engine-written bounce/complaint entries, your own additions, and read-only Unitpost-wide blocks.

List suppressed addresses

GET/api/v1/suppressions

GET /suppressions
curl -X GET 'https://www.unitpost.com/api/v1/suppressions' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "suppression",
      "id": "id_123",
      "email": "customer@example.com",
      "scope": "workspace",
      "reason": "bounce",
      "detail": "string",
      "note": "string",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Retrieve a list of email addresses that are blocked from receiving your mail, newest-first and cursor-paginated. The list includes your workspace's own suppressions AND the read-only Unitpost-wide (platform) entries that would block a send — so it's the complete answer to "why isn't this address getting mail?". Pass ?scope=workspace to hide platform rows. Add an address with Suppress an address or lift a block with Un-suppress an address. Requires the suppressions:read capability.

Parameters4 fields
FieldTypeDescription
limitinteger · queryMax rows to return (default 20, max 100).
afterstring · queryReturn rows after this id (forward paging).
beforestring · queryReturn rows before this id (backward paging).
scopestring · queryFilter by scope. `workspace` returns only your own suppressions; omit (or any other value) to also include read-only `platform` rows.
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
emailrequiredstring—
scoperequiredenumworkspace | platform—
reasonrequiredenumbounce | complaint | unsubscribe | manual | import | api—
detailrequiredobject—
noterequiredobject—
created_atrequiredstring—
updated_atrequiredstring—
200401403422

Suppress an address

POST/api/v1/suppressions

POST /suppressions
curl -X POST 'https://www.unitpost.com/api/v1/suppressions' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{ }'
Response · 200
{
  "object": "suppression",
  "id": "id_123",
  "email": "customer@example.com",
  "scope": "workspace",
  "reason": "bounce",
  "detail": "string",
  "note": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Block one address ({ email }) or up to 1,000 at once ({ emails: [...] }) from receiving any future sends from your workspace. The operation is idempotent — re-suppressing an existing address returns 200 rather than a conflict. The bulk form returns an { object: "list", added, skipped, total } summary. Suppression is the hard stop; to let recipients opt out of just one kind of email, use topics (see Create a topic). Undo with Un-suppress an address. Requires the suppressions:write capability.

Request body4 fields
FieldTypeDescription
emailstring—
emailsstring[]—
reasonenummanual | import | api—
notestring—
200201401403422

Retrieve a suppressed address

GET/api/v1/suppressions/{id}

GET /suppressions/{id}
curl -X GET 'https://www.unitpost.com/api/v1/suppressions/123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "suppression",
  "id": "id_123",
  "email": "customer@example.com",
  "scope": "workspace",
  "reason": "bounce",
  "detail": "string",
  "note": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}

Retrieve one suppression by id (supp_…) or by the email address itself. Returns your own workspace row, or a read-only platform row when the address is blocked Unitpost-wide. Lift a workspace block with Un-suppress an address. Requires the suppressions:read capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · pathA suppression id (`supp_…`) or the email address.
Response · 200 fields9 fields
FieldTypeDescription
objectrequiredstring—
idrequiredstring—
emailrequiredstring—
scoperequiredenumworkspace | platform—
reasonrequiredenumbounce | complaint | unsubscribe | manual | import | api—
detailrequiredobject—
noterequiredobject—
created_atrequiredstring—
updated_atrequiredstring—
200401403404

Un-suppress an address

DELETE/api/v1/suppressions/{id}

DELETE /suppressions/{id}
curl -X DELETE 'https://www.unitpost.com/api/v1/suppressions/123' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'

Remove a workspace suppression by id or email so the address can receive your mail again. The email form is idempotent — removing an address that isn't suppressed still returns 204. Unitpost-wide (platform) entries can't be removed here (403) — contact support. Check what's blocking an address first with Retrieve a suppressed address. Requires the suppressions:write capability.

Parameters1 field
FieldTypeDescription
idrequiredstring · pathA suppression id (`supp_…`) or the email address.
204401403404

Usage

Your workspace's current billing-period usage: emails sent, and on paid plans the dollar sending wallet (monthly allowance, spend, purchased credit, overage) in integer USD cents.

Retrieve usage

GET/api/v1/usage

GET /usage
curl -X GET 'https://www.unitpost.com/api/v1/usage' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0'
Response · 200
{
  "object": "usage",
  "plan": "string",
  "period_start": "string",
  "period_end": "string",
  "emails": {
    "sent": 0,
    "included": 0,
    "daily_cap": 0,
    "sent_today": 0
  },
  "wallet": {
    "allowance_cents": 0,
    "used_cents": 0,
    "carryover_cents": 0,
    "purchased_credit_cents": 0,
    "overage_cents": 0,
    "overage_cap_cents": 0,
    "email_rate_per_1k_cents": 0
  }
}

Retrieve your workspace's current billing-period usage snapshot: the plan, the period window, and the email counter. On paid plans the response also carries the dollar sending wallet — the monthly allowance included with the plan, spend so far, any upgrade carryover, the non-expiring purchased-credit balance, and billable overage — all as integer USD cents. On the free plan wallet is null and the emails object carries the monthly quota and daily cap instead. Amounts are read from the same meters that gate sending, so this matches the dashboard exactly. Requires the emails:read capability.

Response · 200 fields6 fields
FieldTypeDescription
objectrequiredstring—
planrequiredstring—
period_startrequiredstring—
period_endrequiredstring—
emailsrequiredobject—
walletrequiredobject—
200401403

Search docs and guides

Search the docs and product guides.