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 -
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",
"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.
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
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 -
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"}'
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"
}
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",
"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.
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 -
contacts.tag_idsarray of integer -
contacts.list_idsarray of integer -
contacts.consent_sourcestring - Where consent was captured. Recorded for compliance evidence.
-
contacts.consent_ipstring -
tag_idsarray of integer - Applied to every contact in the batch.
-
list_idsarray of integer - Applied to every contact in the batch.
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","plan":"pro"},"status_if_new":"active","tag_ids":[3,8],"list_ids":[1],"consent_source":"signup_form","consent_ip":"203.0.113.24"}],"tag_ids":[1],"list_ids":[1]}'
Request body
{
"contacts": [
{
"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"
}
],
"tag_ids": [
1
],
"list_ids": [
1
]
}
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 -
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",
"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 -
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",
"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 -
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",
"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",
"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",
"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 - Must sit on a fully verified sending domain, or the send is refused.
-
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 - Must sit on a fully verified sending domain, or the send is refused.
-
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": "postal",
"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 - Must be on a fully verified sending 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"
}
}