REST API v1
API Reference
All 84 endpoints. Click any one to see the fields you send and the shape you get back. Prefer to generate a client? Take the OpenAPI spec.
No endpoint matches that filter.
Identity
Who the presented key belongs to
GET
/me
Describe the presented credential
Returns the owning team, the key's scopes, and the applicable rate limit.
Response
-
dataobject -
data.teamobject -
data.api_keyobject or null -
data.rate_limitobject
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/me \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"team": {
"id": 9,
"name": "Acme",
"plan": "business"
},
"api_key": {
"id": 4,
"name": "CRM sync",
"scopes": [
"contacts:read",
"contacts:write"
],
"legacy": false
},
"rate_limit": {
"requests_per_minute": 600
}
}
}
Contacts
Contacts, lists, tags, and segments
GET
/contacts
List contacts
Requires scope contacts:read
Cursor-paginated. Filterable by status, tag, list, segment, and modification time.
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
-
statusstring - Filter by contact status.
-
activependingunsubscribedbounced -
tag_idinteger - Only contacts carrying this tag.
-
list_idinteger - Only contacts on this list.
-
segment_idinteger - Only contacts matching this saved segment.
-
updated_sincestring - ISO 8601 timestamp. Only contacts modified since. Use this to run an incremental sync.
-
includestring - Comma-separated relations to embed: tags, lists.
Response
-
dataarray of object -
data.idinteger -
data.emailstring -
data.first_namestring or null -
data.last_namestring or null -
data.statusstring -
activependingunsubscribedbounced -
data.metadataobject -
data.subscribed_atstring or null -
data.unsubscribed_atstring or null -
data.confirmed_atstring or null -
data.consent_sourcestring or null - How consent was obtained: `api`, `import`, `manual`, `double_opt_in`, or the value your integration supplied.
-
data.consent_attested_atstring or null - When the sender attested that this contact gave permission to be emailed.
-
data.soft_bounce_countinteger -
data.created_atstring or null -
data.updated_atstring or null -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/contacts \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 42,
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Okonkwo",
"status": "active",
"metadata": {
"company": "Acme",
"city": "Copenhagen"
},
"subscribed_at": "2026-08-01T09:15:00+00:00",
"unsubscribed_at": null,
"confirmed_at": null,
"consent_source": "signup_form",
"consent_attested_at": "2026-08-01T09:15:00+00:00",
"soft_bounce_count": 0,
"created_at": "2026-08-01T09:15:00+00:00",
"updated_at": "2026-08-09T14:02:00+00:00"
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
POST
/contacts
Create or update a contact
Requires scope contacts:write
Upserts on email address. An existing contact that has unsubscribed or bounced keeps that status regardless of what is sent. Use `status_if_new` to control only what a brand-new contact is created as. `consent_attested` must be `true` (your confirmation that the person gave permission to receive email from you and was not taken from a purchased, rented or scraped list), otherwise the request is refused with 422.
Body
-
emailstring Required -
first_namestring -
last_namestring -
metadataobject - Arbitrary key/value data, addressable as merge tags.
-
status_if_newstring - Status for a contact that does not already exist. Has no effect on an existing contact.
-
activepending -
tag_idsarray of integer -
list_idsarray of integer -
consent_sourcestring - Where consent was captured. Recorded for compliance evidence.
-
consent_ipstring -
consent_attestedboolean Required - Required, must be `true`. Confirms this person gave permission to receive email from you and was not taken from a purchased, rented or scraped list. Recorded as `consent_attested_at` on a new contact.
-
1
Response
-
dataobject -
data.idinteger -
data.emailstring -
data.first_namestring or null -
data.last_namestring or null -
data.statusstring -
activependingunsubscribedbounced -
data.metadataobject -
data.subscribed_atstring or null -
data.unsubscribed_atstring or null -
data.confirmed_atstring or null -
data.consent_sourcestring or null - How consent was obtained: `api`, `import`, `manual`, `double_opt_in`, or the value your integration supplied.
-
data.consent_attested_atstring or null - When the sender attested that this contact gave permission to be emailed.
-
data.soft_bounce_countinteger -
data.created_atstring or null -
data.updated_atstring or null
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/contacts \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"jane@example.com","first_name":"Jane","last_name":"Okonkwo","metadata":{"company":"Acme","plan":"pro"},"status_if_new":"active","tag_ids":[3,8],"list_ids":[1],"consent_source":"signup_form","consent_ip":"203.0.113.24","consent_attested":true}'
Request body
{
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Okonkwo",
"metadata": {
"company": "Acme",
"plan": "pro"
},
"status_if_new": "active",
"tag_ids": [
3,
8
],
"list_ids": [
1
],
"consent_source": "signup_form",
"consent_ip": "203.0.113.24",
"consent_attested": true
}
Response 200
{
"data": {
"id": 42,
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Okonkwo",
"status": "active",
"metadata": {
"company": "Acme",
"city": "Copenhagen"
},
"subscribed_at": "2026-08-01T09:15:00+00:00",
"unsubscribed_at": null,
"confirmed_at": null,
"consent_source": "signup_form",
"consent_attested_at": "2026-08-01T09:15:00+00:00",
"soft_bounce_count": 0,
"created_at": "2026-08-01T09:15:00+00:00",
"updated_at": "2026-08-09T14:02:00+00:00"
}
}
POST
/contacts/bulk
Bulk upsert contacts
Requires scope contacts:write
Up to 1,000 per call. Returns a per-row result so a partial success is legible. `consent_attested` must be `true` (your confirmation that every contact in the batch gave permission to receive email from you and the list is not purchased, rented or scraped), otherwise the whole batch is refused with 422.
Body
-
contactsarray of object Required -
contacts.emailstring Required -
contacts.first_namestring -
contacts.last_namestring -
contacts.metadataobject - Arbitrary key/value data, addressable as merge tags.
-
contacts.status_if_newstring - Status for a contact that does not already exist. Has no effect on an existing contact.
-
activepending -
tag_idsarray of integer - Applied to every contact in the batch.
-
list_idsarray of integer - Applied to every contact in the batch.
-
consent_attestedboolean Required - Required, must be `true`. Confirms every contact in the batch gave permission to receive email from you and the list is not purchased, rented or scraped. Recorded as `consent_attested_at` on each new contact.
-
1 -
consent_sourcestring - Where consent was captured, applied to new contacts in the batch. Defaults to `api`.
Response
-
dataarray of object -
data.emailstring -
data.idinteger -
data.createdboolean -
data.statusstring -
createdinteger -
updatedinteger
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/contacts/bulk \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contacts":[{"email":"jane@example.com","first_name":"Jane","last_name":"Okonkwo","metadata":{"company":"Acme"},"status_if_new":"active"}],"tag_ids":[1],"list_ids":[1],"consent_attested":true,"consent_source":"signup_form"}'
Request body
{
"contacts": [
{
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Okonkwo",
"metadata": {
"company": "Acme"
},
"status_if_new": "active"
}
],
"tag_ids": [
1
],
"list_ids": [
1
],
"consent_attested": true,
"consent_source": "signup_form"
}
Response 200
{
"data": [
{
"email": "string",
"id": 1,
"created": true,
"status": "string"
}
],
"created": 1,
"updated": 1
}
GET
/contacts/{contact}
Fetch a contact
Requires scope contacts:read
Accepts either a numeric id or an email address.
Path parameters
-
contactstring Required - Contact id, or the contact's email address.
Response
-
dataobject -
data.idinteger -
data.emailstring -
data.first_namestring or null -
data.last_namestring or null -
data.statusstring -
activependingunsubscribedbounced -
data.metadataobject -
data.subscribed_atstring or null -
data.unsubscribed_atstring or null -
data.confirmed_atstring or null -
data.consent_sourcestring or null - How consent was obtained: `api`, `import`, `manual`, `double_opt_in`, or the value your integration supplied.
-
data.consent_attested_atstring or null - When the sender attested that this contact gave permission to be emailed.
-
data.soft_bounce_countinteger -
data.created_atstring or null -
data.updated_atstring or null
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/contacts/:contact \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 42,
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Okonkwo",
"status": "active",
"metadata": {
"company": "Acme",
"city": "Copenhagen"
},
"subscribed_at": "2026-08-01T09:15:00+00:00",
"unsubscribed_at": null,
"confirmed_at": null,
"consent_source": "signup_form",
"consent_attested_at": "2026-08-01T09:15:00+00:00",
"soft_bounce_count": 0,
"created_at": "2026-08-01T09:15:00+00:00",
"updated_at": "2026-08-09T14:02:00+00:00"
}
}
PATCH
/contacts/{contact}
Update a contact
Requires scope contacts:write
Setting `status` to `unsubscribed` also creates a suppression; setting it back to `active` lifts only the unsubscribe suppression, never a bounce or complaint.
Path parameters
-
contactstring Required - Contact id, or the contact's email address.
Body
-
first_namestring or null -
last_namestring or null -
metadataobject -
statusstring -
activependingunsubscribed -
tag_idsarray of integer - Replaces the contact's tags.
-
list_idsarray of integer - Replaces the contact's list memberships.
Response
-
dataobject -
data.idinteger -
data.emailstring -
data.first_namestring or null -
data.last_namestring or null -
data.statusstring -
activependingunsubscribedbounced -
data.metadataobject -
data.subscribed_atstring or null -
data.unsubscribed_atstring or null -
data.confirmed_atstring or null -
data.consent_sourcestring or null - How consent was obtained: `api`, `import`, `manual`, `double_opt_in`, or the value your integration supplied.
-
data.consent_attested_atstring or null - When the sender attested that this contact gave permission to be emailed.
-
data.soft_bounce_countinteger -
data.created_atstring or null -
data.updated_atstring or null
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X PATCH http://tweak.email/api/v1/contacts/:contact \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"first_name":"string","last_name":"string","metadata":{},"status":"active","tag_ids":[1],"list_ids":[1]}'
Request body
{
"first_name": "string",
"last_name": "string",
"metadata": {},
"status": "active",
"tag_ids": [
1
],
"list_ids": [
1
]
}
Response 200
{
"data": {
"id": 42,
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Okonkwo",
"status": "active",
"metadata": {
"company": "Acme",
"city": "Copenhagen"
},
"subscribed_at": "2026-08-01T09:15:00+00:00",
"unsubscribed_at": null,
"confirmed_at": null,
"consent_source": "signup_form",
"consent_attested_at": "2026-08-01T09:15:00+00:00",
"soft_bounce_count": 0,
"created_at": "2026-08-01T09:15:00+00:00",
"updated_at": "2026-08-09T14:02:00+00:00"
}
}
DELETE
/contacts/{contact}
Delete a contact
Requires scope contacts:write
Soft delete. For GDPR erasure use the erase endpoint instead.
Path parameters
-
contactstring Required - Contact id, or the contact's email address.
Response
-
dataobject -
data.idinteger -
data.deletedboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X DELETE http://tweak.email/api/v1/contacts/:contact \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 42,
"deleted": true
}
}
POST
/contacts/{contact}/erase
Erase a contact (GDPR Art. 17)
Requires scope contacts:write
Permanently removes the contact and anonymises their delivery events. The suppression record is deliberately retained so an erased opt-out is never lost.
Path parameters
-
contactstring Required - Contact id or email address.
Response
-
dataobject -
data.contacts_deletedinteger -
data.events_anonymizedinteger
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/contacts/:contact/erase \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"contacts_deleted": 1,
"events_anonymized": 1
}
}
GET
/lists
List contact lists
Requires scope contacts:read
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
Response
-
dataarray of object -
data.idinteger -
data.namestring -
data.descriptionstring or null -
data.is_defaultboolean -
data.opt_in_modestring -
singledouble -
data.contacts_countinteger -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/lists \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 1,
"name": "string",
"description": "string",
"is_default": true,
"opt_in_mode": "single",
"contacts_count": 1
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
POST
/lists
Create a contact list
Requires scope contacts:write
Body
-
namestring Required -
descriptionstring -
opt_in_modestring - Double opt-in creates new contacts as `pending` until they confirm.
-
singledouble
Response
-
dataobject -
data.idinteger -
data.namestring -
data.descriptionstring or null -
data.is_defaultboolean -
data.opt_in_modestring -
singledouble -
data.contacts_countinteger
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/lists \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","description":"string","opt_in_mode":"single"}'
Request body
{
"name": "string",
"description": "string",
"opt_in_mode": "single"
}
Response 200
{
"data": {
"id": 1,
"name": "string",
"description": "string",
"is_default": true,
"opt_in_mode": "single",
"contacts_count": 1
}
}
GET
/lists/{list}
Fetch a list
Requires scope contacts:read
Path parameters
-
listinteger Required - List id.
Response
-
dataobject -
data.idinteger -
data.namestring -
data.descriptionstring or null -
data.is_defaultboolean -
data.opt_in_modestring -
singledouble -
data.contacts_countinteger
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/lists/:list \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 1,
"name": "string",
"description": "string",
"is_default": true,
"opt_in_mode": "single",
"contacts_count": 1
}
}
PATCH
/lists/{list}
Update a list
Requires scope contacts:write
Path parameters
-
listinteger Required - List id.
Body
-
namestring Required -
descriptionstring -
opt_in_modestring - Double opt-in creates new contacts as `pending` until they confirm.
-
singledouble
Response
-
dataobject -
data.idinteger -
data.namestring -
data.descriptionstring or null -
data.is_defaultboolean -
data.opt_in_modestring -
singledouble -
data.contacts_countinteger
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X PATCH http://tweak.email/api/v1/lists/:list \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","description":"string","opt_in_mode":"single"}'
Request body
{
"name": "string",
"description": "string",
"opt_in_mode": "single"
}
Response 200
{
"data": {
"id": 1,
"name": "string",
"description": "string",
"is_default": true,
"opt_in_mode": "single",
"contacts_count": 1
}
}
DELETE
/lists/{list}
Delete a list
Requires scope contacts:write
The default list cannot be deleted.
Path parameters
-
listinteger Required - List id.
Response
-
dataobject -
data.idinteger -
data.deletedboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X DELETE http://tweak.email/api/v1/lists/:list \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 42,
"deleted": true
}
}
GET
/lists/{list}/contacts
List the contacts on a list
Requires scope contacts:read
Path parameters
-
listinteger Required - List id.
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
Response
-
dataarray of object -
data.idinteger -
data.emailstring -
data.first_namestring or null -
data.last_namestring or null -
data.statusstring -
activependingunsubscribedbounced -
data.metadataobject -
data.subscribed_atstring or null -
data.unsubscribed_atstring or null -
data.confirmed_atstring or null -
data.consent_sourcestring or null - How consent was obtained: `api`, `import`, `manual`, `double_opt_in`, or the value your integration supplied.
-
data.consent_attested_atstring or null - When the sender attested that this contact gave permission to be emailed.
-
data.soft_bounce_countinteger -
data.created_atstring or null -
data.updated_atstring or null -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/lists/:list/contacts \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 42,
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Okonkwo",
"status": "active",
"metadata": {
"company": "Acme",
"city": "Copenhagen"
},
"subscribed_at": "2026-08-01T09:15:00+00:00",
"unsubscribed_at": null,
"confirmed_at": null,
"consent_source": "signup_form",
"consent_attested_at": "2026-08-01T09:15:00+00:00",
"soft_bounce_count": 0,
"created_at": "2026-08-01T09:15:00+00:00",
"updated_at": "2026-08-09T14:02:00+00:00"
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
POST
/lists/{list}/contacts
Attach contacts to a list
Requires scope contacts:write
Ids belonging to another team are silently skipped and reported in the `skipped` count.
Path parameters
-
listinteger Required - List id.
Body
-
contact_idsarray of integer Required
Response
-
dataobject -
data.list_idinteger -
data.attachedinteger -
data.skippedinteger - Ids that did not resolve to a contact on your team.
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/lists/:list/contacts \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contact_ids":[1]}'
Request body
{
"contact_ids": [
1
]
}
Response 200
{
"data": {
"list_id": 1,
"attached": 1,
"skipped": 1
}
}
DELETE
/lists/{list}/contacts/{contact}
Remove a contact from a list
Requires scope contacts:write
Detaches the membership only. The contact itself is untouched.
Path parameters
-
listinteger Required - List id.
-
contactinteger Required - Contact id.
Response
-
dataobject -
data.list_idinteger -
data.contact_idinteger -
data.detachedboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X DELETE http://tweak.email/api/v1/lists/:list/contacts/:contact \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"list_id": 7,
"contact_id": 42,
"detached": true
}
}
GET
/segments
List segments
Requires scope contacts:read
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
Response
-
dataarray of object -
data.idinteger -
data.namestring -
data.definitionobject -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/segments \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 1,
"name": "string",
"definition": {
"tag_ids": [
1
],
"date_from": "2026-08-09",
"date_to": "2026-08-09",
"search": "string",
"company": "string",
"location": "string",
"engagement": {
"type": "opened",
"days": 1
}
}
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
POST
/segments
Create a segment
Requires scope contacts:write
Body
-
namestring Required -
definitionobject -
definition.tag_idsarray of integer -
definition.date_fromstring or null -
definition.date_tostring or null -
definition.searchstring or null - Matches email, first name, or last name.
-
definition.companystring or null - Matches the contact metadata `company` field.
-
definition.locationstring or null - Matches metadata city, country, or location.
-
definition.engagementobject or null - Restrict to contacts by email engagement over a trailing window. Machine opens (Apple MPP and bot prefetches) are excluded. Note that only the SendGrid integration flags them, so on other providers positive open conditions may still be over-inclusive.
Response
-
dataobject -
data.idinteger -
data.namestring -
data.definitionobject
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/segments \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","definition":{"tag_ids":[1],"date_from":"2026-08-09","date_to":"2026-08-09","search":"string","company":"string","location":"string","engagement":{"type":"opened","days":1}}}'
Request body
{
"name": "string",
"definition": {
"tag_ids": [
1
],
"date_from": "2026-08-09",
"date_to": "2026-08-09",
"search": "string",
"company": "string",
"location": "string",
"engagement": {
"type": "opened",
"days": 1
}
}
}
Response 200
{
"data": {
"id": 1,
"name": "string",
"definition": {
"tag_ids": [
1
],
"date_from": "2026-08-09",
"date_to": "2026-08-09",
"search": "string",
"company": "string",
"location": "string",
"engagement": {
"type": "opened",
"days": 1
}
}
}
}
POST
/segments/preview
Preview an unsaved segment definition
Requires scope contacts:read
Read-only. Returns the resolved contact count plus a 10-record sample, so a definition can be iterated on before it is committed.
Body
-
definitionobject -
definition.tag_idsarray of integer -
definition.date_fromstring or null -
definition.date_tostring or null -
definition.searchstring or null - Matches email, first name, or last name.
-
definition.companystring or null - Matches the contact metadata `company` field.
-
definition.locationstring or null - Matches metadata city, country, or location.
-
definition.engagementobject or null - Restrict to contacts by email engagement over a trailing window. Machine opens (Apple MPP and bot prefetches) are excluded. Note that only the SendGrid integration flags them, so on other providers positive open conditions may still be over-inclusive.
Response
-
dataobject -
data.countinteger - Active contacts the definition resolves to.
-
data.filtersobject -
data.samplearray of object
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/segments/preview \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"definition":{"tag_ids":[1],"date_from":"2026-08-09","date_to":"2026-08-09","search":"string","company":"string","location":"string","engagement":{"type":"opened","days":1}}}'
Request body
{
"definition": {
"tag_ids": [
1
],
"date_from": "2026-08-09",
"date_to": "2026-08-09",
"search": "string",
"company": "string",
"location": "string",
"engagement": {
"type": "opened",
"days": 1
}
}
}
Response 200
{
"data": {
"count": 1,
"filters": {
"tag_ids": [
1
],
"date_from": "2026-08-09",
"date_to": "2026-08-09",
"search": "string",
"company": "string",
"location": "string",
"engagement": {
"type": "opened",
"days": 1
}
},
"sample": [
{
"id": 42,
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Okonkwo",
"status": "active",
"metadata": {
"company": "Acme",
"city": "Copenhagen"
},
"subscribed_at": "2026-08-01T09:15:00+00:00",
"unsubscribed_at": null,
"confirmed_at": null,
"consent_source": "signup_form",
"consent_attested_at": "2026-08-01T09:15:00+00:00",
"soft_bounce_count": 0,
"created_at": "2026-08-01T09:15:00+00:00",
"updated_at": "2026-08-09T14:02:00+00:00"
}
]
}
}
GET
/segments/{segment}
Fetch a segment
Requires scope contacts:read
Path parameters
-
segmentinteger Required - Segment id.
Response
-
dataobject -
data.idinteger -
data.namestring -
data.definitionobject
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/segments/:segment \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 1,
"name": "string",
"definition": {
"tag_ids": [
1
],
"date_from": "2026-08-09",
"date_to": "2026-08-09",
"search": "string",
"company": "string",
"location": "string",
"engagement": {
"type": "opened",
"days": 1
}
}
}
}
PATCH
/segments/{segment}
Update a segment
Requires scope contacts:write
Path parameters
-
segmentinteger Required - Segment id.
Body
-
namestring Required -
definitionobject -
definition.tag_idsarray of integer -
definition.date_fromstring or null -
definition.date_tostring or null -
definition.searchstring or null - Matches email, first name, or last name.
-
definition.companystring or null - Matches the contact metadata `company` field.
-
definition.locationstring or null - Matches metadata city, country, or location.
-
definition.engagementobject or null - Restrict to contacts by email engagement over a trailing window. Machine opens (Apple MPP and bot prefetches) are excluded. Note that only the SendGrid integration flags them, so on other providers positive open conditions may still be over-inclusive.
Response
-
dataobject -
data.idinteger -
data.namestring -
data.definitionobject
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X PATCH http://tweak.email/api/v1/segments/:segment \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","definition":{"tag_ids":[1],"date_from":"2026-08-09","date_to":"2026-08-09","search":"string","company":"string","location":"string","engagement":{"type":"opened","days":1}}}'
Request body
{
"name": "string",
"definition": {
"tag_ids": [
1
],
"date_from": "2026-08-09",
"date_to": "2026-08-09",
"search": "string",
"company": "string",
"location": "string",
"engagement": {
"type": "opened",
"days": 1
}
}
}
Response 200
{
"data": {
"id": 1,
"name": "string",
"definition": {
"tag_ids": [
1
],
"date_from": "2026-08-09",
"date_to": "2026-08-09",
"search": "string",
"company": "string",
"location": "string",
"engagement": {
"type": "opened",
"days": 1
}
}
}
}
DELETE
/segments/{segment}
Delete a segment
Requires scope contacts:write
Path parameters
-
segmentinteger Required - Segment id.
Response
-
dataobject -
data.idinteger -
data.deletedboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X DELETE http://tweak.email/api/v1/segments/:segment \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 42,
"deleted": true
}
}
POST
/segments/{segment}/preview
Preview a saved segment
Requires scope contacts:read
Read-only: resolved count plus a sample.
Path parameters
-
segmentinteger Required - Segment id.
Response
-
dataobject -
data.countinteger - Active contacts the definition resolves to.
-
data.filtersobject -
data.samplearray of object
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/segments/:segment/preview \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"count": 1,
"filters": {
"tag_ids": [
1
],
"date_from": "2026-08-09",
"date_to": "2026-08-09",
"search": "string",
"company": "string",
"location": "string",
"engagement": {
"type": "opened",
"days": 1
}
},
"sample": [
{
"id": 42,
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Okonkwo",
"status": "active",
"metadata": {
"company": "Acme",
"city": "Copenhagen"
},
"subscribed_at": "2026-08-01T09:15:00+00:00",
"unsubscribed_at": null,
"confirmed_at": null,
"consent_source": "signup_form",
"consent_attested_at": "2026-08-01T09:15:00+00:00",
"soft_bounce_count": 0,
"created_at": "2026-08-01T09:15:00+00:00",
"updated_at": "2026-08-09T14:02:00+00:00"
}
]
}
}
Campaigns
Campaign authoring and sending
GET
/campaigns
List campaigns
Requires scope campaigns:read
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
-
statusstring - Filter by campaign status.
Response
-
dataarray of object -
data.idinteger -
data.namestring -
data.subjectstring or null -
data.preheaderstring or null -
data.from_namestring or null -
data.from_emailstring or null -
data.reply_tostring or null -
data.statusstring -
draftscheduledscreeningheldqueuingsendingpausedsentpartialcancelledfailed -
data.audienceobject -
data.template_idinteger or null -
data.ab_testobject -
data.total_recipientsinteger -
data.sent_countinteger -
data.failed_countinteger -
data.failure_reasonstring or null -
data.scheduled_atstring or null -
data.sent_atstring or null -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/campaigns \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 118,
"name": "August product update",
"subject": "What's new this month",
"preheader": "Three things we shipped",
"from_name": "Jane at Acme",
"from_email": "hello@acme.com",
"reply_to": "support@acme.com",
"status": "sent",
"audience": {
"source": "list",
"contact_list_id": 1,
"filters": null
},
"template_id": 12,
"ab_test": {
"enabled": false,
"percentage": null,
"winner_metric": null,
"delay_minutes": null,
"winner_variant_id": null
},
"total_recipients": 12480,
"sent_count": 12463,
"failed_count": 17,
"failure_reason": null,
"scheduled_at": null,
"sent_at": "2026-08-09T10:00:00+00:00"
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
POST
/campaigns
Create a campaign
Requires scope campaigns:write
Created as a draft. Set the body separately via the content endpoint.
Body
-
namestring Required -
subjectstring -
preheaderstring -
from_namestring -
from_emailstring - Sent as-is from a fully verified sending domain. On tweak.email without one, the message goes out from your team address on the shared sender domain with this address as Reply-To. Third-party providers require a verified domain.
-
reply_tostring -
audience_sourcestring -
listbigquery -
contact_list_idinteger or null -
segment_idinteger or null - Copies the segment's filter definition onto the campaign.
-
audience_filtersobject -
audience_filters.tag_idsarray of integer -
audience_filters.date_fromstring or null -
audience_filters.date_tostring or null -
audience_filters.searchstring or null - Matches email, first name, or last name.
-
audience_filters.companystring or null - Matches the contact metadata `company` field.
-
audience_filters.locationstring or null - Matches metadata city, country, or location.
-
audience_filters.engagementobject or null - Restrict to contacts by email engagement over a trailing window. Machine opens (Apple MPP and bot prefetches) are excluded. Note that only the SendGrid integration flags them, so on other providers positive open conditions may still be over-inclusive.
-
template_idinteger or null -
ab_test_enabledboolean -
ab_test_percentageinteger -
ab_winner_metricstring -
opensclicks -
ab_test_delay_minutesinteger
Response
-
dataobject -
data.idinteger -
data.namestring -
data.subjectstring or null -
data.preheaderstring or null -
data.from_namestring or null -
data.from_emailstring or null -
data.reply_tostring or null -
data.statusstring -
draftscheduledscreeningheldqueuingsendingpausedsentpartialcancelledfailed -
data.audienceobject -
data.template_idinteger or null -
data.ab_testobject -
data.total_recipientsinteger -
data.sent_countinteger -
data.failed_countinteger -
data.failure_reasonstring or null -
data.scheduled_atstring or null -
data.sent_atstring or null
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/campaigns \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","subject":"string","preheader":"string","from_name":"string","from_email":"jane@example.com","reply_to":"jane@example.com","audience_source":"list","contact_list_id":1,"segment_id":1,"audience_filters":{"tag_ids":[1],"date_from":"2026-08-09","date_to":"2026-08-09","search":"string","company":"string","location":"string","engagement":{"type":"opened","days":1}},"template_id":1,"ab_test_enabled":true,"ab_test_percentage":1,"ab_winner_metric":"opens","ab_test_delay_minutes":1}'
Request body
{
"name": "string",
"subject": "string",
"preheader": "string",
"from_name": "string",
"from_email": "jane@example.com",
"reply_to": "jane@example.com",
"audience_source": "list",
"contact_list_id": 1,
"segment_id": 1,
"audience_filters": {
"tag_ids": [
1
],
"date_from": "2026-08-09",
"date_to": "2026-08-09",
"search": "string",
"company": "string",
"location": "string",
"engagement": {
"type": "opened",
"days": 1
}
},
"template_id": 1,
"ab_test_enabled": true,
"ab_test_percentage": 1,
"ab_winner_metric": "opens",
"ab_test_delay_minutes": 1
}
Response 200
{
"data": {
"id": 118,
"name": "August product update",
"subject": "What's new this month",
"preheader": "Three things we shipped",
"from_name": "Jane at Acme",
"from_email": "hello@acme.com",
"reply_to": "support@acme.com",
"status": "sent",
"audience": {
"source": "list",
"contact_list_id": 1,
"filters": null
},
"template_id": 12,
"ab_test": {
"enabled": false,
"percentage": null,
"winner_metric": null,
"delay_minutes": null,
"winner_variant_id": null
},
"total_recipients": 12480,
"sent_count": 12463,
"failed_count": 17,
"failure_reason": null,
"scheduled_at": null,
"sent_at": "2026-08-09T10:00:00+00:00"
}
}
GET
/campaigns/{campaign}
Fetch a campaign
Requires scope campaigns:read
Path parameters
-
campaigninteger Required - Campaign id.
Response
-
dataobject -
data.idinteger -
data.namestring -
data.subjectstring or null -
data.preheaderstring or null -
data.from_namestring or null -
data.from_emailstring or null -
data.reply_tostring or null -
data.statusstring -
draftscheduledscreeningheldqueuingsendingpausedsentpartialcancelledfailed -
data.audienceobject -
data.template_idinteger or null -
data.ab_testobject -
data.total_recipientsinteger -
data.sent_countinteger -
data.failed_countinteger -
data.failure_reasonstring or null -
data.scheduled_atstring or null -
data.sent_atstring or null
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/campaigns/:campaign \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 118,
"name": "August product update",
"subject": "What's new this month",
"preheader": "Three things we shipped",
"from_name": "Jane at Acme",
"from_email": "hello@acme.com",
"reply_to": "support@acme.com",
"status": "sent",
"audience": {
"source": "list",
"contact_list_id": 1,
"filters": null
},
"template_id": 12,
"ab_test": {
"enabled": false,
"percentage": null,
"winner_metric": null,
"delay_minutes": null,
"winner_variant_id": null
},
"total_recipients": 12480,
"sent_count": 12463,
"failed_count": 17,
"failure_reason": null,
"scheduled_at": null,
"sent_at": "2026-08-09T10:00:00+00:00"
}
}
PATCH
/campaigns/{campaign}
Update a campaign
Requires scope campaigns:write
Refused with 422 once the campaign is screening, sending, or sent.
Path parameters
-
campaigninteger Required - Campaign id.
Body
-
namestring Required -
subjectstring -
preheaderstring -
from_namestring -
from_emailstring - Sent as-is from a fully verified sending domain. On tweak.email without one, the message goes out from your team address on the shared sender domain with this address as Reply-To. Third-party providers require a verified domain.
-
reply_tostring -
audience_sourcestring -
listbigquery -
contact_list_idinteger or null -
segment_idinteger or null - Copies the segment's filter definition onto the campaign.
-
audience_filtersobject -
audience_filters.tag_idsarray of integer -
audience_filters.date_fromstring or null -
audience_filters.date_tostring or null -
audience_filters.searchstring or null - Matches email, first name, or last name.
-
audience_filters.companystring or null - Matches the contact metadata `company` field.
-
audience_filters.locationstring or null - Matches metadata city, country, or location.
-
audience_filters.engagementobject or null - Restrict to contacts by email engagement over a trailing window. Machine opens (Apple MPP and bot prefetches) are excluded. Note that only the SendGrid integration flags them, so on other providers positive open conditions may still be over-inclusive.
-
template_idinteger or null -
ab_test_enabledboolean -
ab_test_percentageinteger -
ab_winner_metricstring -
opensclicks -
ab_test_delay_minutesinteger
Response
-
dataobject -
data.idinteger -
data.namestring -
data.subjectstring or null -
data.preheaderstring or null -
data.from_namestring or null -
data.from_emailstring or null -
data.reply_tostring or null -
data.statusstring -
draftscheduledscreeningheldqueuingsendingpausedsentpartialcancelledfailed -
data.audienceobject -
data.template_idinteger or null -
data.ab_testobject -
data.total_recipientsinteger -
data.sent_countinteger -
data.failed_countinteger -
data.failure_reasonstring or null -
data.scheduled_atstring or null -
data.sent_atstring or null
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X PATCH http://tweak.email/api/v1/campaigns/:campaign \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","subject":"string","preheader":"string","from_name":"string","from_email":"jane@example.com","reply_to":"jane@example.com","audience_source":"list","contact_list_id":1,"segment_id":1,"audience_filters":{"tag_ids":[1],"date_from":"2026-08-09","date_to":"2026-08-09","search":"string","company":"string","location":"string","engagement":{"type":"opened","days":1}},"template_id":1,"ab_test_enabled":true,"ab_test_percentage":1,"ab_winner_metric":"opens","ab_test_delay_minutes":1}'
Request body
{
"name": "string",
"subject": "string",
"preheader": "string",
"from_name": "string",
"from_email": "jane@example.com",
"reply_to": "jane@example.com",
"audience_source": "list",
"contact_list_id": 1,
"segment_id": 1,
"audience_filters": {
"tag_ids": [
1
],
"date_from": "2026-08-09",
"date_to": "2026-08-09",
"search": "string",
"company": "string",
"location": "string",
"engagement": {
"type": "opened",
"days": 1
}
},
"template_id": 1,
"ab_test_enabled": true,
"ab_test_percentage": 1,
"ab_winner_metric": "opens",
"ab_test_delay_minutes": 1
}
Response 200
{
"data": {
"id": 118,
"name": "August product update",
"subject": "What's new this month",
"preheader": "Three things we shipped",
"from_name": "Jane at Acme",
"from_email": "hello@acme.com",
"reply_to": "support@acme.com",
"status": "sent",
"audience": {
"source": "list",
"contact_list_id": 1,
"filters": null
},
"template_id": 12,
"ab_test": {
"enabled": false,
"percentage": null,
"winner_metric": null,
"delay_minutes": null,
"winner_variant_id": null
},
"total_recipients": 12480,
"sent_count": 12463,
"failed_count": 17,
"failure_reason": null,
"scheduled_at": null,
"sent_at": "2026-08-09T10:00:00+00:00"
}
}
DELETE
/campaigns/{campaign}
Delete a campaign
Requires scope campaigns:write
Path parameters
-
campaigninteger Required - Campaign id.
Response
-
dataobject -
data.idinteger -
data.deletedboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X DELETE http://tweak.email/api/v1/campaigns/:campaign \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 42,
"deleted": true
}
}
PUT
/campaigns/{campaign}/content
Set campaign content
Requires scope campaigns:write
Supplying `template_id` copies that template's body in, so a later edit to the template cannot change a campaign somebody already reviewed.
Path parameters
-
campaigninteger Required - Campaign id.
Body
-
html_contentstring or null -
plain_contentstring or null -
blocks_jsonarray or null -
template_idinteger or null
Response
-
dataobject -
data.idinteger -
data.namestring -
data.subjectstring or null -
data.preheaderstring or null -
data.from_namestring or null -
data.from_emailstring or null -
data.reply_tostring or null -
data.statusstring -
draftscheduledscreeningheldqueuingsendingpausedsentpartialcancelledfailed -
data.audienceobject -
data.template_idinteger or null -
data.ab_testobject -
data.total_recipientsinteger -
data.sent_countinteger -
data.failed_countinteger -
data.failure_reasonstring or null -
data.scheduled_atstring or null -
data.sent_atstring or null
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X PUT http://tweak.email/api/v1/campaigns/:campaign/content \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html_content":"string","plain_content":"string","blocks_json":[{}],"template_id":1}'
Request body
{
"html_content": "string",
"plain_content": "string",
"blocks_json": [
{}
],
"template_id": 1
}
Response 200
{
"data": {
"id": 118,
"name": "August product update",
"subject": "What's new this month",
"preheader": "Three things we shipped",
"from_name": "Jane at Acme",
"from_email": "hello@acme.com",
"reply_to": "support@acme.com",
"status": "sent",
"audience": {
"source": "list",
"contact_list_id": 1,
"filters": null
},
"template_id": 12,
"ab_test": {
"enabled": false,
"percentage": null,
"winner_metric": null,
"delay_minutes": null,
"winner_variant_id": null
},
"total_recipients": 12480,
"sent_count": 12463,
"failed_count": 17,
"failure_reason": null,
"scheduled_at": null,
"sent_at": "2026-08-09T10:00:00+00:00"
}
}
POST
/campaigns/{campaign}/variants
Replace the A/B variant set
Requires scope campaigns:write
Variants are replaced wholesale, and A/B testing is switched on.
Path parameters
-
campaigninteger Required - Campaign id.
Body
-
variantsarray of object Required -
variants.labelstring Required -
variants.subjectstring Required -
variants.from_namestring
Response
-
dataarray of object -
data.idinteger -
data.labelstring -
data.subjectstring -
data.from_namestring or null -
data.is_winnerboolean -
data.sentinteger -
data.openedinteger -
data.clickedinteger -
data.open_ratenumber -
data.click_ratenumber
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/campaigns/:campaign/variants \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"variants":[{"label":"string","subject":"string","from_name":"string"}]}'
Request body
{
"variants": [
{
"label": "string",
"subject": "string",
"from_name": "string"
}
]
}
Response 200
{
"data": [
{
"id": 1,
"label": "string",
"subject": "string",
"from_name": "string",
"is_winner": true,
"sent": 1,
"opened": 1,
"clicked": 1,
"open_rate": 1,
"click_rate": 1
}
]
}
POST
/campaigns/{campaign}/preflight
Dry-run the send checks
Requires scope campaigns:read
Read-only. Returns every reason a send would currently be refused, plus the estimated recipient count.
Path parameters
-
campaigninteger Required - Campaign id.
Response
-
dataobject -
data.campaign_idinteger -
data.statusstring -
data.launchableboolean -
data.blockersarray of string -
data.estimated_recipientsinteger or null - Null for a BigQuery audience, which is resolved at send time.
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/campaigns/:campaign/preflight \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"campaign_id": 1,
"status": "string",
"launchable": true,
"blockers": [
"string"
],
"estimated_recipients": 1
}
}
POST
/campaigns/{campaign}/send
Send a campaign
Requires scope campaigns:send
Hands the campaign to abuse screening, which releases it to the send pipeline. Returns 422 with a `blockers` array if any send gate fails.
Send an `Idempotency-Key` header: a retried send is the one mistake in this API that cannot be undone.
Path parameters
-
campaigninteger Required - Campaign id.
Response
-
dataobject -
data.idinteger -
data.namestring -
data.subjectstring or null -
data.preheaderstring or null -
data.from_namestring or null -
data.from_emailstring or null -
data.reply_tostring or null -
data.statusstring -
draftscheduledscreeningheldqueuingsendingpausedsentpartialcancelledfailed -
data.audienceobject -
data.template_idinteger or null -
data.ab_testobject -
data.total_recipientsinteger -
data.sent_countinteger -
data.failed_countinteger -
data.failure_reasonstring or null -
data.scheduled_atstring or null -
data.sent_atstring or null
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/campaigns/:campaign/send \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 118,
"name": "August product update",
"subject": "What's new this month",
"preheader": "Three things we shipped",
"from_name": "Jane at Acme",
"from_email": "hello@acme.com",
"reply_to": "support@acme.com",
"status": "sent",
"audience": {
"source": "list",
"contact_list_id": 1,
"filters": null
},
"template_id": 12,
"ab_test": {
"enabled": false,
"percentage": null,
"winner_metric": null,
"delay_minutes": null,
"winner_variant_id": null
},
"total_recipients": 12480,
"sent_count": 12463,
"failed_count": 17,
"failure_reason": null,
"scheduled_at": null,
"sent_at": "2026-08-09T10:00:00+00:00"
}
}
POST
/campaigns/{campaign}/schedule
Schedule a campaign
Requires scope campaigns:send
Runs the same gates as an immediate send.
Path parameters
-
campaigninteger Required - Campaign id.
Body
-
send_atstring Required - Must be in the future. Interpreted as UTC unless an offset is given.
Response
-
dataobject -
data.idinteger -
data.namestring -
data.subjectstring or null -
data.preheaderstring or null -
data.from_namestring or null -
data.from_emailstring or null -
data.reply_tostring or null -
data.statusstring -
draftscheduledscreeningheldqueuingsendingpausedsentpartialcancelledfailed -
data.audienceobject -
data.template_idinteger or null -
data.ab_testobject -
data.total_recipientsinteger -
data.sent_countinteger -
data.failed_countinteger -
data.failure_reasonstring or null -
data.scheduled_atstring or null -
data.sent_atstring or null
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/campaigns/:campaign/schedule \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"send_at":"2026-08-09T10:00:00+00:00"}'
Request body
{
"send_at": "2026-08-09T10:00:00+00:00"
}
Response 200
{
"data": {
"id": 118,
"name": "August product update",
"subject": "What's new this month",
"preheader": "Three things we shipped",
"from_name": "Jane at Acme",
"from_email": "hello@acme.com",
"reply_to": "support@acme.com",
"status": "sent",
"audience": {
"source": "list",
"contact_list_id": 1,
"filters": null
},
"template_id": 12,
"ab_test": {
"enabled": false,
"percentage": null,
"winner_metric": null,
"delay_minutes": null,
"winner_variant_id": null
},
"total_recipients": 12480,
"sent_count": 12463,
"failed_count": 17,
"failure_reason": null,
"scheduled_at": null,
"sent_at": "2026-08-09T10:00:00+00:00"
}
}
POST
/campaigns/{campaign}/cancel
Cancel a campaign
Requires scope campaigns:send
Only a draft, scheduled, or paused campaign can be cancelled: once chunks are dispatched the messages are already gone.
Path parameters
-
campaigninteger Required - Campaign id.
Response
-
dataobject -
data.idinteger -
data.namestring -
data.subjectstring or null -
data.preheaderstring or null -
data.from_namestring or null -
data.from_emailstring or null -
data.reply_tostring or null -
data.statusstring -
draftscheduledscreeningheldqueuingsendingpausedsentpartialcancelledfailed -
data.audienceobject -
data.template_idinteger or null -
data.ab_testobject -
data.total_recipientsinteger -
data.sent_countinteger -
data.failed_countinteger -
data.failure_reasonstring or null -
data.scheduled_atstring or null -
data.sent_atstring or null
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/campaigns/:campaign/cancel \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 118,
"name": "August product update",
"subject": "What's new this month",
"preheader": "Three things we shipped",
"from_name": "Jane at Acme",
"from_email": "hello@acme.com",
"reply_to": "support@acme.com",
"status": "sent",
"audience": {
"source": "list",
"contact_list_id": 1,
"filters": null
},
"template_id": 12,
"ab_test": {
"enabled": false,
"percentage": null,
"winner_metric": null,
"delay_minutes": null,
"winner_variant_id": null
},
"total_recipients": 12480,
"sent_count": 12463,
"failed_count": 17,
"failure_reason": null,
"scheduled_at": null,
"sent_at": "2026-08-09T10:00:00+00:00"
}
}
POST
/campaigns/{campaign}/test
Send test messages
Requires scope campaigns:send
Up to 5 addresses. Counts against the plan's email allowance, because these are real sends.
Path parameters
-
campaigninteger Required - Campaign id.
Body
-
emailsarray of string Required
Response
-
dataobject -
data.sentarray of string -
data.failedarray of object
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/campaigns/:campaign/test \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"emails":["jane@example.com"]}'
Request body
{
"emails": [
"jane@example.com"
]
}
Response 200
{
"data": {
"sent": [
"string"
],
"failed": [
{}
]
}
}
Reporting
Delivery events and campaign performance
GET
/campaigns/{campaign}/report
Campaign performance
Requires scope events:read
Opens exclude machine/bot opens (Apple MPP), so the rates reflect human engagement.
Path parameters
-
campaigninteger Required - Campaign id.
Response
-
dataobject -
data.campaign_idinteger -
data.namestring -
data.subjectstring or null -
data.statusstring -
data.sent_atstring or null -
data.metricsobject
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/campaigns/:campaign/report \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"campaign_id": 1,
"name": "string",
"subject": "string",
"status": "string",
"sent_at": "2026-08-09T10:00:00+00:00",
"metrics": {
"recipients": 12480,
"sent": 12463,
"delivered": 12301,
"opened": 5238,
"clicked": 1104,
"bounced": 162,
"unsubscribed": 38,
"complained": 3,
"delivery_rate": 98.7,
"open_rate": 42.6,
"click_rate": 9,
"ctor": 21.1,
"bounce_rate": 1.3,
"unsubscribe_rate": 0.3,
"complaint_rate": 0.02
}
}
}
GET
/campaigns/{campaign}/report/links
Per-link click breakdown
Requires scope events:read
Path parameters
-
campaigninteger Required - Campaign id.
Response
-
dataarray of object -
data.urlstring -
data.clicksinteger - Human clicks, with detected bot traffic removed.
-
data.uniqueinteger -
data.botsinteger
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/campaigns/:campaign/report/links \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"url": "https://acme.com/changelog",
"clicks": 412,
"unique": 388,
"bots": 27
}
]
}
GET
/campaigns/{campaign}/report/variants
Per-variant A/B results
Requires scope events:read
Path parameters
-
campaigninteger Required - Campaign id.
Response
-
dataarray of object -
data.idinteger -
data.labelstring -
data.subjectstring -
data.from_namestring or null -
data.is_winnerboolean -
data.sentinteger -
data.openedinteger -
data.clickedinteger -
data.open_ratenumber -
data.click_ratenumber
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/campaigns/:campaign/report/variants \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 1,
"label": "string",
"subject": "string",
"from_name": "string",
"is_winner": true,
"sent": 1,
"opened": 1,
"clicked": 1,
"open_rate": 1,
"click_rate": 1
}
]
}
GET
/campaigns/{campaign}/recipients
Per-recipient delivery detail
Requires scope events:read
Who received the campaign and whether they opened or clicked.
Path parameters
-
campaigninteger Required - Campaign id.
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
-
filterstring - Narrow to a delivery outcome.
-
allopenedclickedbouncedpendingsent
Response
-
dataarray of object -
data.contact_idinteger or null -
data.emailstring or null -
data.statusstring -
data.sent_atstring or null -
data.opened_atstring or null -
data.clicked_atstring or null -
data.error_messagestring or null -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/campaigns/:campaign/recipients \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"contact_id": 1,
"email": "string",
"status": "string",
"sent_at": "string",
"opened_at": "string",
"clicked_at": "string",
"error_message": "string"
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
GET
/events
List delivery events
Requires scope events:read
The read side of the event pipeline, bounded by a 180-day retention window. Machine opens are excluded unless explicitly requested.
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
-
eventstring - Filter by event type.
-
deliveredbounceddeferreddroppedspam_reportunsubscribedopenedclickedqueuedsent -
emailstring - Filter by recipient address.
-
campaign_idinteger - Filter by campaign.
-
contact_idinteger - Filter by contact.
-
sincestring - ISO 8601 lower bound.
-
untilstring - ISO 8601 upper bound.
-
include_machine_opensboolean - Include Apple MPP and bot prefetch opens.
Response
-
dataarray of object -
data.idinteger -
data.emailstring -
data.eventstring -
data.campaign_idinteger or null -
data.contact_idinteger or null -
data.providerstring or null -
data.sourcestring or null -
data.is_machine_openboolean -
data.metadataobject -
data.created_atstring or null -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/events \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 90211,
"email": "jane@example.com",
"event": "clicked",
"campaign_id": 118,
"contact_id": 42,
"provider": "ses",
"source": "webhook",
"is_machine_open": false,
"metadata": {
"url": "https://acme.com/changelog"
},
"created_at": "2026-08-09T10:04:12+00:00"
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
POST
/events
Ingest delivery events
Requires scope events:write
Up to 1,000 per call. Events flow through the same pipeline as provider webhooks: campaign stats update, suppressions are created, contact statuses sync.
Body
-
eventsarray of object Required -
events.emailstring Required -
events.eventstring Required -
deliveredbounceddeferreddroppedspam_reportunsubscribedopenedclickedqueuedsent -
events.campaign_idinteger or null -
events.contact_idinteger or null -
events.providerstring -
events.sourcestring -
events.metadataobject
Response
-
statusstring -
processedinteger
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/events \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"events":[{"email":"jane@example.com","event":"delivered","campaign_id":1,"contact_id":1,"provider":"string","source":"string","metadata":{}}]}'
Request body
{
"events": [
{
"email": "jane@example.com",
"event": "delivered",
"campaign_id": 1,
"contact_id": 1,
"provider": "string",
"source": "string",
"metadata": {}
}
]
}
Response 200
{
"status": "string",
"processed": 1
}
Templates
Reusable email templates
GET
/templates
List templates
Requires scope campaigns:read
Includes the platform's system starter templates alongside your own.
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
-
scopestring - Which templates to return.
-
allsystemteam -
categorystring - Filter by category.
Response
-
dataarray of object -
data.idinteger -
data.namestring -
data.categorystring or null -
data.stylestring or null -
data.descriptionstring or null -
data.is_systemboolean -
data.html_contentstring or null - Only returned when fetching a single template.
-
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/templates \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 1,
"name": "string",
"category": "string",
"style": "string",
"description": "string",
"is_system": true,
"html_content": "string"
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
POST
/templates
Create a template
Requires scope campaigns:write
Body
-
namestring Required -
categorystring -
stylestring -
descriptionstring -
html_contentstring -
blocks_jsonarray of object
Response
-
dataobject -
data.idinteger -
data.namestring -
data.categorystring or null -
data.stylestring or null -
data.descriptionstring or null -
data.is_systemboolean -
data.html_contentstring or null - Only returned when fetching a single template.
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/templates \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","category":"string","style":"string","description":"string","html_content":"string","blocks_json":[{}]}'
Request body
{
"name": "string",
"category": "string",
"style": "string",
"description": "string",
"html_content": "string",
"blocks_json": [
{}
]
}
Response 200
{
"data": {
"id": 1,
"name": "string",
"category": "string",
"style": "string",
"description": "string",
"is_system": true,
"html_content": "string"
}
}
GET
/templates/{template}
Fetch a template
Requires scope campaigns:read
Unlike the listing, this includes the full body.
Path parameters
-
templateinteger Required - Template id.
Response
-
dataobject -
data.idinteger -
data.namestring -
data.categorystring or null -
data.stylestring or null -
data.descriptionstring or null -
data.is_systemboolean -
data.html_contentstring or null - Only returned when fetching a single template.
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/templates/:template \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 1,
"name": "string",
"category": "string",
"style": "string",
"description": "string",
"is_system": true,
"html_content": "string"
}
}
PATCH
/templates/{template}
Update a template
Requires scope campaigns:write
System templates cannot be modified.
Path parameters
-
templateinteger Required - Template id.
Body
-
namestring Required -
categorystring -
stylestring -
descriptionstring -
html_contentstring -
blocks_jsonarray of object
Response
-
dataobject -
data.idinteger -
data.namestring -
data.categorystring or null -
data.stylestring or null -
data.descriptionstring or null -
data.is_systemboolean -
data.html_contentstring or null - Only returned when fetching a single template.
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X PATCH http://tweak.email/api/v1/templates/:template \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","category":"string","style":"string","description":"string","html_content":"string","blocks_json":[{}]}'
Request body
{
"name": "string",
"category": "string",
"style": "string",
"description": "string",
"html_content": "string",
"blocks_json": [
{}
]
}
Response 200
{
"data": {
"id": 1,
"name": "string",
"category": "string",
"style": "string",
"description": "string",
"is_system": true,
"html_content": "string"
}
}
DELETE
/templates/{template}
Delete a template
Requires scope campaigns:write
Path parameters
-
templateinteger Required - Template id.
Response
-
dataobject -
data.idinteger -
data.deletedboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X DELETE http://tweak.email/api/v1/templates/:template \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 42,
"deleted": true
}
}
Sequences
Automated sequences and enrolment
GET
/sequences
List sequences
Requires scope campaigns:read
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
Response
-
dataarray of object -
data.idinteger -
data.namestring -
data.descriptionstring or null -
data.statusstring -
data.trigger_typestring or null -
data.subscribers_countinteger -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/sequences \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 1,
"name": "string",
"description": "string",
"status": "string",
"trigger_type": "string",
"subscribers_count": 1
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
GET
/sequences/{sequence}
Fetch a sequence
Requires scope campaigns:read
Path parameters
-
sequenceinteger Required - Sequence id.
Response
-
dataobject -
data.idinteger -
data.namestring -
data.descriptionstring or null -
data.statusstring -
data.trigger_typestring or null -
data.subscribers_countinteger
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/sequences/:sequence \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 1,
"name": "string",
"description": "string",
"status": "string",
"trigger_type": "string",
"subscribers_count": 1
}
}
GET
/sequences/{sequence}/subscribers
List subscribers
Requires scope campaigns:read
Path parameters
-
sequenceinteger Required - Sequence id.
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
Response
-
dataarray of object -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/sequences/:sequence/subscribers \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
POST
/sequences/{sequence}/subscribers
Enrol contacts
Requires scope contacts:write
Accepts contact ids or email addresses. This is how you trigger onboarding from your own signup flow.
Path parameters
-
sequenceinteger Required - Sequence id.
Body
-
contact_idsarray of integer -
emailsarray of string
Response
-
dataobject -
data.sequence_idinteger -
data.matchedinteger -
data.enrolledinteger -
data.skippedinteger - Matched but not eligible: already enrolled, unsubscribed, or bounced.
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/sequences/:sequence/subscribers \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contact_ids":[1],"emails":["jane@example.com"]}'
Request body
{
"contact_ids": [
1
],
"emails": [
"jane@example.com"
]
}
Response 200
{
"data": {
"sequence_id": 1,
"matched": 1,
"enrolled": 1,
"skipped": 1
}
}
DELETE
/sequences/{sequence}/subscribers/{contact}
Remove a contact from a sequence
Requires scope contacts:write
Stops any pending steps. `removed` is false when the contact was not enrolled.
Path parameters
-
sequenceinteger Required - Sequence id.
-
contactinteger Required - Contact id.
Response
-
dataobject -
data.sequence_idinteger -
data.contact_idinteger -
data.removedboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X DELETE http://tweak.email/api/v1/sequences/:sequence/subscribers/:contact \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"sequence_id": 3,
"contact_id": 42,
"removed": true
}
}
Waitlists
Waitlists and signups
GET
/waitlists
List waitlists
Requires scope contacts:read
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
Response
-
dataarray of object -
data.idinteger -
data.namestring -
data.slugstring -
data.statusstring -
data.public_urlstring -
data.signups_countinteger -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/waitlists \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 1,
"name": "string",
"slug": "string",
"status": "string",
"public_url": "string",
"signups_count": 1
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
GET
/waitlists/{waitlist}
Fetch a waitlist
Requires scope contacts:read
Path parameters
-
waitlistinteger Required - Waitlist id.
Response
-
dataobject -
data.idinteger -
data.namestring -
data.slugstring -
data.statusstring -
data.public_urlstring -
data.signups_countinteger
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/waitlists/:waitlist \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 1,
"name": "string",
"slug": "string",
"status": "string",
"public_url": "string",
"signups_count": 1
}
}
GET
/waitlists/{waitlist}/signups
List signups
Requires scope contacts:read
Path parameters
-
waitlistinteger Required - Waitlist id.
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
Response
-
dataarray of object -
data.idinteger -
data.emailstring -
data.namestring or null -
data.positioninteger or null -
data.statusstring -
data.referral_codestring -
data.referrals_countinteger -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/waitlists/:waitlist/signups \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 1,
"email": "string",
"name": "string",
"position": 1,
"status": "string",
"referral_code": "string",
"referrals_count": 1
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
POST
/waitlists/{waitlist}/signups
Add a signup
Requires scope contacts:write
Queue position is assigned server-side.
Path parameters
-
waitlistinteger Required - Waitlist id.
Body
-
emailstring Required -
namestring -
referral_codestring - The referring signup's code, if this signup came through a referral.
-
metadataobject
Response
-
dataobject -
data.idinteger -
data.emailstring -
data.namestring or null -
data.positioninteger or null -
data.statusstring -
data.referral_codestring -
data.referrals_countinteger
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/waitlists/:waitlist/signups \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"jane@example.com","name":"string","referral_code":"string","metadata":{}}'
Request body
{
"email": "jane@example.com",
"name": "string",
"referral_code": "string",
"metadata": {}
}
Response 200
{
"data": {
"id": 1,
"email": "string",
"name": "string",
"position": 1,
"status": "string",
"referral_code": "string",
"referrals_count": 1
}
}
POST
/waitlists/{waitlist}/invite
Invite signups
Requires scope contacts:write
Either specific ids, or `count` to take the next N from the front of the queue.
Path parameters
-
waitlistinteger Required - Waitlist id.
Body
-
signup_idsarray of integer -
countinteger - Invite the next N by queue position.
Response
-
dataobject -
data.waitlist_idinteger -
data.invitedinteger -
data.signup_idsarray of integer
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/waitlists/:waitlist/invite \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"signup_ids":[1],"count":1}'
Request body
{
"signup_ids": [
1
],
"count": 1
}
Response 200
{
"data": {
"waitlist_id": 1,
"invited": 1,
"signup_ids": [
1
]
}
}
Suppressions
The do-not-mail list
GET
/suppressions
List suppressions
Requires scope suppressions:read
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
-
reasonstring - Filter by reason.
-
hard_bouncesoft_bouncespam_reportunsubscribemanual -
emailstring - Exact address match.
Response
-
dataarray of object -
data.idinteger -
data.emailstring -
data.reasonstring -
hard_bouncesoft_bouncespam_reportunsubscribemanual -
data.sourcestring or null -
data.campaign_idinteger or null -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/suppressions \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 1,
"email": "string",
"reason": "hard_bounce",
"source": "string",
"campaign_id": 1
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
POST
/suppressions
Add a suppression
Requires scope suppressions:write
Upserts on address.
Body
-
emailstring Required -
reasonstring Required -
hard_bouncespam_reportunsubscribemanual -
metadataobject
Response
-
dataobject -
data.idinteger -
data.emailstring -
data.reasonstring -
hard_bouncesoft_bouncespam_reportunsubscribemanual -
data.sourcestring or null -
data.campaign_idinteger or null
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/suppressions \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"jane@example.com","reason":"hard_bounce","metadata":{}}'
Request body
{
"email": "jane@example.com",
"reason": "hard_bounce",
"metadata": {}
}
Response 200
{
"data": {
"id": 1,
"email": "string",
"reason": "hard_bounce",
"source": "string",
"campaign_id": 1
}
}
DELETE
/suppressions
Remove a suppression
Requires scope suppressions:write
The address becomes eligible for future sends.
Body
-
emailstring Required
Response
-
dataobject -
data.statusstring
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X DELETE http://tweak.email/api/v1/suppressions \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"jane@example.com"}'
Request body
{
"email": "jane@example.com"
}
Response 200
{
"data": {
"status": "ok"
}
}
Transactional
One-off transactional sending
POST
/send
Send a transactional message
Requires scope transactional:send
Honours suppressions, sender-domain verification, and the plan's email allowance. Platform-wide suppressions (confirmed hard bounces and complaints) cannot be bypassed.
Body
-
tostring Required -
to_namestring -
from_emailstring Required - Sent as-is from a fully verified sending domain. On tweak.email without one, the message goes out from your team address on the shared sender domain with this address as Reply-To. Third-party providers require a verified domain.
-
from_namestring -
reply_tostring -
subjectstring Required - Merge tags are substituted here too.
-
htmlstring - Required unless template_id is given.
-
textstring - Derived from the HTML when omitted.
-
template_idinteger -
variablesobject - Values for {{merge_tags}} in the subject and body.
-
ignore_suppressionboolean - Waives your own suppression list for a genuinely operational message. Never waives platform-wide suppressions.
Response
-
dataobject -
data.statusstring -
sentsandboxed -
data.tostring -
data.subjectstring -
data.deliveredboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/send \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to":"jane@example.com","to_name":"string","from_email":"jane@example.com","from_name":"string","reply_to":"jane@example.com","subject":"string","html":"string","text":"string","template_id":1,"variables":{},"ignore_suppression":true}'
Request body
{
"to": "jane@example.com",
"to_name": "string",
"from_email": "jane@example.com",
"from_name": "string",
"reply_to": "jane@example.com",
"subject": "string",
"html": "string",
"text": "string",
"template_id": 1,
"variables": {},
"ignore_suppression": true
}
Response 200
{
"data": {
"status": "sent",
"to": "string",
"subject": "string",
"delivered": true
}
}
AI
Copy polish, subject lines, and deliverability scoring
POST
/ai/polish
Polish email copy
Requires scope ai:write
Rewrites for a requested tone and goal, and returns concrete suggestions plus a quality score. Counts against the plan's monthly AI allowance.
Body
-
contentstring Required -
tonestring -
professionalcasualfriendlyformalpersuasive -
goalstring -
clarityengagementconversionbrevitywarmth
Response
-
dataobject -
data.idinteger -
data.tweaked_contentstring -
data.ai_suggestionsarray of string -
data.quality_scorenumber -
data.model_usedstring or null -
data.tokens_usedinteger
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/ai/polish \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content":"string","tone":"professional","goal":"clarity"}'
Request body
{
"content": "string",
"tone": "professional",
"goal": "clarity"
}
Response 200
{
"data": {
"id": 1,
"tweaked_content": "string",
"ai_suggestions": [
"string"
],
"quality_score": 1,
"model_used": "string",
"tokens_used": 1
}
}
POST
/ai/subject-lines
Generate and score subject lines
Requires scope ai:write
Set `use_history` to ground the scoring in this team's own historical open rates rather than generic heuristics.
Body
-
subjectstring Required -
use_historyboolean - Ground scoring in this team's real historical open rates.
Response
-
dataobject -
data.idinteger -
data.variationsarray of object -
data.winning_subjectstring -
data.original_scorenumber -
data.best_scorenumber
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/ai/subject-lines \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"subject":"string","use_history":true}'
Request body
{
"subject": "string",
"use_history": true
}
Response 200
{
"data": {
"id": 1,
"variations": [
{}
],
"winning_subject": "string",
"original_score": 1,
"best_score": 1
}
}
POST
/ai/deliverability
Score a message for deliverability
Requires scope ai:write
Returns a spam score, specific issues, and a pass/warn/fail verdict. Useful as a pre-send check in CI.
Body
-
contentstring Required -
subjectstring -
use_account_signalsboolean - Weight the assessment by your account's real bounce, complaint, and domain-auth signals. Defaults to true.
Response
-
dataobject -
data.idinteger -
data.spam_scorenumber - 0–100; lower is better.
-
data.issuesarray of object -
data.checks_passedarray of object -
data.verdictstring -
passwarnfail
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/ai/deliverability \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content":"string","subject":"string","use_account_signals":true}'
Request body
{
"content": "string",
"subject": "string",
"use_account_signals": true
}
Response 200
{
"data": {
"id": 1,
"spam_score": 1,
"issues": [
{}
],
"checks_passed": [
{}
],
"verdict": "pass"
}
}
GET
/ai/usage
Remaining AI and email allowance
Response
-
dataobject -
data.period_startstring -
data.period_endstring -
data.ai_tweaksobject -
data.subject_testsobject -
data.emailsobject
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/ai/usage \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"period_start": "2026-08-09T10:00:00+00:00",
"period_end": "2026-08-09T10:00:00+00:00",
"ai_tweaks": {},
"subject_tests": {},
"emails": {}
}
}
Webhooks
Outbound event subscriptions
GET
/webhooks
List webhook endpoints
Requires scope webhooks:read
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
Response
-
dataarray of object -
data.idinteger -
data.urlstring -
data.descriptionstring or null -
data.eventsarray of string -
data.is_activeboolean -
data.last_delivered_atstring or null -
data.consecutive_failuresinteger -
data.secretstring - Returned only on creation and rotation.
-
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/webhooks \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 1,
"url": "string",
"description": "string",
"events": [
"string"
],
"is_active": true,
"last_delivered_at": "2026-08-09T10:00:00+00:00",
"consecutive_failures": 1,
"secret": "string"
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
POST
/webhooks
Create a webhook endpoint
Requires scope webhooks:write
The response is the only time the signing secret is returned. Payloads are signed as `Tweak-Signature: t={timestamp},v1={hmac}` where the HMAC is SHA-256 over `{timestamp}.{raw body}`.
Body
-
urlstring Required - Must be publicly routable. Private and reserved IPs are rejected.
-
descriptionstring -
eventsarray of string Required - Event types, or ["*"] for all.
Response
-
dataobject -
data.idinteger -
data.urlstring -
data.descriptionstring or null -
data.eventsarray of string -
data.is_activeboolean -
data.last_delivered_atstring or null -
data.consecutive_failuresinteger -
data.secretstring - Returned only on creation and rotation.
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/webhooks \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/hook","description":"string","events":["string"]}'
Request body
{
"url": "https://example.com/hook",
"description": "string",
"events": [
"string"
]
}
Response 200
{
"data": {
"id": 1,
"url": "string",
"description": "string",
"events": [
"string"
],
"is_active": true,
"last_delivered_at": "2026-08-09T10:00:00+00:00",
"consecutive_failures": 1,
"secret": "string"
}
}
GET
/webhooks/event-types
List subscribable event types
Requires scope webhooks:read
Response
-
dataarray of object -
data.typestring -
data.descriptionstring
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/webhooks/event-types \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"type": "string",
"description": "string"
}
]
}
GET
/webhooks/{endpoint}
Fetch a webhook endpoint
Requires scope webhooks:read
Path parameters
-
endpointinteger Required - Webhook endpoint id.
Response
-
dataobject -
data.idinteger -
data.urlstring -
data.descriptionstring or null -
data.eventsarray of string -
data.is_activeboolean -
data.last_delivered_atstring or null -
data.consecutive_failuresinteger -
data.secretstring - Returned only on creation and rotation.
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/webhooks/:endpoint \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 1,
"url": "string",
"description": "string",
"events": [
"string"
],
"is_active": true,
"last_delivered_at": "2026-08-09T10:00:00+00:00",
"consecutive_failures": 1,
"secret": "string"
}
}
PATCH
/webhooks/{endpoint}
Update a webhook endpoint
Requires scope webhooks:write
Path parameters
-
endpointinteger Required - Webhook endpoint id.
Body
-
urlstring -
descriptionstring -
eventsarray of string -
is_activeboolean
Response
-
dataobject -
data.idinteger -
data.urlstring -
data.descriptionstring or null -
data.eventsarray of string -
data.is_activeboolean -
data.last_delivered_atstring or null -
data.consecutive_failuresinteger -
data.secretstring - Returned only on creation and rotation.
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X PATCH http://tweak.email/api/v1/webhooks/:endpoint \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/hook","description":"string","events":["string"],"is_active":true}'
Request body
{
"url": "https://example.com/hook",
"description": "string",
"events": [
"string"
],
"is_active": true
}
Response 200
{
"data": {
"id": 1,
"url": "string",
"description": "string",
"events": [
"string"
],
"is_active": true,
"last_delivered_at": "2026-08-09T10:00:00+00:00",
"consecutive_failures": 1,
"secret": "string"
}
}
DELETE
/webhooks/{endpoint}
Delete a webhook endpoint
Requires scope webhooks:write
Path parameters
-
endpointinteger Required - Webhook endpoint id.
Response
-
dataobject -
data.idinteger -
data.deletedboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X DELETE http://tweak.email/api/v1/webhooks/:endpoint \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 42,
"deleted": true
}
}
GET
/webhooks/{endpoint}/deliveries
Delivery history
Requires scope webhooks:read
What was sent and what your endpoint answered, for debugging.
Path parameters
-
endpointinteger Required - Webhook endpoint id.
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
Response
-
dataarray of object -
data.idinteger -
data.event_typestring -
data.event_idstring -
data.payloadobject -
data.response_statusinteger or null -
data.response_bodystring or null -
data.attemptinteger -
data.succeededboolean -
data.errorstring or null -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/webhooks/:endpoint/deliveries \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 1,
"event_type": "string",
"event_id": "string",
"payload": {},
"response_status": 1,
"response_body": "string",
"attempt": 1,
"succeeded": true,
"error": "string"
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
POST
/webhooks/{endpoint}/test
Send a test event
Requires scope webhooks:write
Lets you verify signature checking without provoking a real bounce.
Path parameters
-
endpointinteger Required - Webhook endpoint id.
Response
-
dataobject -
data.statusstring
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/webhooks/:endpoint/test \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"status": "ok"
}
}
POST
/webhooks/{endpoint}/rotate-secret
Rotate the signing secret
Requires scope webhooks:write
Returns the new secret once.
Path parameters
-
endpointinteger Required - Webhook endpoint id.
Response
-
dataobject -
data.idinteger -
data.urlstring -
data.descriptionstring or null -
data.eventsarray of string -
data.is_activeboolean -
data.last_delivered_atstring or null -
data.consecutive_failuresinteger -
data.secretstring - Returned only on creation and rotation.
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/webhooks/:endpoint/rotate-secret \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"id": 1,
"url": "string",
"description": "string",
"events": [
"string"
],
"is_active": true,
"last_delivered_at": "2026-08-09T10:00:00+00:00",
"consecutive_failures": 1,
"secret": "string"
}
}
API keys
Credential management
GET
/api-keys
List API keys
Secrets are never returned; only a masked prefix.
Query parameters
-
per_pageinteger default 25 - Records per page (1–100, default 25).
-
cursorstring - The `next_cursor` value from a previous response.
Response
-
dataarray of object -
data.idinteger -
data.namestring -
data.masked_keystring -
data.scopesarray of string -
data.last_used_atstring or null -
data.expires_atstring or null -
data.revoked_atstring or null -
per_pageinteger -
next_cursorstring or null -
prev_cursorstring or null -
next_page_urlstring or null -
prev_page_urlstring or null -
has_moreboolean
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/api-keys \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"id": 1,
"name": "string",
"masked_key": "string",
"scopes": [
"string"
],
"last_used_at": "2026-08-09T10:00:00+00:00",
"expires_at": "2026-08-09T10:00:00+00:00",
"revoked_at": "2026-08-09T10:00:00+00:00"
}
],
"per_page": 1,
"next_cursor": "string",
"prev_cursor": "string",
"next_page_url": "string",
"prev_page_url": "string",
"has_more": true
}
POST
/api-keys
Mint an API key
The secret is returned exactly once. A key cannot grant scopes it does not itself hold.
Body
-
namestring Required -
scopesarray of string - Defaults to every scope the minting key holds.
-
expires_atstring
Response
-
dataobject -
data.idinteger -
data.namestring -
data.masked_keystring -
data.scopesarray of string -
data.last_used_atstring or null -
data.expires_atstring or null -
data.revoked_atstring or null -
data.keystring - The secret. Returned exactly once.
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X POST http://tweak.email/api/v1/api-keys \
-H "Authorization: Bearer $TWEAK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","scopes":["contacts:read"],"expires_at":"2026-08-09T10:00:00+00:00"}'
Request body
{
"name": "string",
"scopes": [
"contacts:read"
],
"expires_at": "2026-08-09T10:00:00+00:00"
}
Response 200
{
"data": {
"id": 1,
"name": "string",
"masked_key": "string",
"scopes": [
"string"
],
"last_used_at": "2026-08-09T10:00:00+00:00",
"expires_at": "2026-08-09T10:00:00+00:00",
"revoked_at": "2026-08-09T10:00:00+00:00",
"key": "string"
}
}
GET
/api-keys/scopes
List available scopes
Response
-
dataarray of object -
data.scopestring -
data.descriptionstring
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl http://tweak.email/api/v1/api-keys/scopes \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": [
{
"scope": "string",
"description": "string"
}
]
}
DELETE
/api-keys/{key}
Revoke an API key
Revoked rather than deleted, so the audit record survives.
Path parameters
-
keyinteger Required - API key id.
Response
-
dataobject -
data.statusstring
Errors
- 403
- The key lacks a required scope, or the plan does not include API access.
- 404
- No such record for this team.
- 422
- The request was understood but could not be applied.
Every endpoint can also return 401 (bad credential) and 429 (rate limited).
Request
curl -X DELETE http://tweak.email/api/v1/api-keys/:key \
-H "Authorization: Bearer $TWEAK_API_KEY"
Response 200
{
"data": {
"status": "ok"
}
}