API reference
Errors
One JSON error shape and every code the API returns.
Every non-2xx response has this body:
{ "error": { "code": "domain_not_verified", "message": "Domain example.com is pending" } }
code is stable and meant for programs. message is for humans and can change.
Codes
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_json |
Body is not valid JSON. |
| 401 | unauthorized |
Missing, malformed, unknown, revoked or expired API key. |
| 403 | forbidden_domain |
The key is restricted to other domains than the from domain. |
| 403 | forbidden |
The endpoint needs a FULL key (DELETE /contacts/:id, DELETE /suppressions/:id). |
| 403 | sending_paused |
An admin or an alert rule paused sending for the organization (also on POST /broadcasts/:id/send). An admin resumes it in Settings → Organization. |
| 403 | test_key_not_allowed |
A f5_test_ key called an endpoint that sends live mail from the worker (POST /broadcasts, POST /broadcasts/:id/send, POST /broadcasts/:id/decide, POST /broadcasts/:id/resend, POST /events, POST /contacts, POST /contacts/:id/subscriptions, DELETE /contacts/:id, POST/DELETE /suppressions). Test keys simulate POST /emails only. |
| 404 | not_found |
No such email / contact / broadcast in this organization. |
| 409 | not_cancelable |
Email is no longer queued (or is being sent right now); broadcast is already sent or canceled. |
| 409 | not_schedulable |
Broadcast is not draft or scheduled. |
| 409 | not_testing |
POST /broadcasts/:id/decide: the broadcast is not testing. |
| 409 | not_resendable |
POST /broadcasts/:id/resend: the parent is not sent, or is itself a resend. |
| 409 | resend_exists |
POST /broadcasts/:id/resend: a child that is not canceled already exists. |
| 409 | resend_too_early |
A resend cannot send until 24 hours after the parent finished. The message names the earliest time. |
| 422 | subject_from_template |
POST /broadcasts/:id/resend: the parent uses a template, so subject is not accepted. |
| 409 | suppression_conflict |
POST /suppressions: the address already has a complaint, hard_bounce or unsubscribe entry at that scope. A manual entry cannot replace it or give it an expiry. |
| 422 | validation_error |
Schema validation failed; the message lists each field, e.g. to: Invalid email; subject: subject is required. |
| 422 | invalid_from |
from is not email or Name <email>. |
| 422 | unknown_domain |
The from domain is not registered in this organization. |
| 422 | domain_not_verified |
The from domain is pending, failed or disabled. |
| 422 | bulk_single_recipient |
A bulk kind (bulk.*, broadcast.*, digest.*, sequence.*) was sent to more than one recipient. |
| 422 | template_not_found |
No template with that id/slug, it is archived, or it has no published version (or not that version). |
| 422 | template_variables |
Template data invalid — message lists missing:, unknown: and mistyped: variables. |
| 422 | invalid_subject |
Rendered subject is empty, over 998 characters or contains line breaks. |
| 422 | unknown_topic |
The topic slug is not valid, or POST /contacts/{id}/subscriptions named a slug that does not exist. |
| 422 | unknown_segment |
segment_id is not one of the segments of the organization. |
| 422 | invalid_segment |
The stored filter of the segment no longer parses. |
| 422 | invalid_cursor |
The cursor on a list endpoint is not one the platform issued. |
| 422 | attachments_not_in_batch |
A POST /emails/batch item carried attachments. Send those messages one at a time. |
| 422 | stream_mismatch |
A bulk kind was sent from a domain whose stream is Transactional (also POST /broadcasts). Send bulk mail from a Bulk domain such as news.<domain>. |
| 422 | reserved_header |
headers names a header the platform sets (Message-ID, Feedback-ID, Return-Path, DKIM-Signature, From, To, Cc, Bcc, Reply-To, Sender, Subject, Date, MIME-Version, Content-Type, Content-Transfer-Encoding, Content-ID, Content-Disposition). |
| 429 | rate_limited |
Over the per-key requests-per-second cap (default 10; the key's own limit or API_RATE_LIMIT_PER_SEC). The response carries Retry-After: 1. |
| 429 | quota_exceeded |
The organization reached its monthly limit. Each recipient counts as one email. On Free the limit is the allowance. On a paid plan, mail past the allowance is sent and billed as overage, up to twice the allowance. Live sends are refused until the next UTC month; Retry-After carries the seconds until then. Test keys are never counted. |
| 429 | contact_quota_exceeded |
POST /events would create more new contacts than the daily quota. The response carries Retry-After in seconds until the next UTC day. |
| 429 | run_quota_exceeded |
POST /events would start more automation runs than the daily quota. The response carries Retry-After in seconds until the next UTC day. |
| 500 | internal_error |
Unexpected failure; safe to retry with the same Idempotency-Key. |
Management endpoints
These come only from the management endpoints, which need an ADMIN key.
| Status | error.code |
Meaning |
|---|---|---|
| 403 | admin_key_not_mintable |
POST /keys will not create an ADMIN key, and POST /keys/{id}/rotate will not rotate one. Mint and rotate ADMIN keys in the console, so a key can never clone its own authority. |
| 409 | already_revoked |
The key is already revoked. |
| 409 | already_rotated |
The key has a replacement already. Rotate the replacement instead. |
| 403 | plan_limit |
The plan's contact or webhook endpoint limit is reached. Updates to existing contacts still work. Upgrade in Settings → Billing, or delete an endpoint. |
| 409 | domain_taken |
The domain belongs to this organization or to another one. |
| 422 | manual_dns |
The domain uses manual DNS. Read records from GET /domains/{id} and publish them yourself. |
| 422 | cloudflare_unconfigured |
The server has no Cloudflare token, so it cannot publish for you. |
| 422 | publish_failed |
Cloudflare refused one or more records. The message carries the provider error. |
| 422 | unsupported_transport |
Only SES domains register a sending identity. |
| 422 | identity_registration_failed |
SES refused the identity, a DKIM selector or the custom MAIL FROM. The message carries the provider error. |
| 409 | topic_exists |
A topic with that slug is already in the organization. |
| 409 | topic_in_use |
A broadcast references the topic. Delete the broadcast first. |
POST /emails/batch never fails as a whole for per-item problems. Each item is { "id" } or { "error": { "code", "message" } } with the same codes.
SMTP replies
The SMTP relay maps each API answer to one SMTP reply:
| API | SMTP reply |
|---|---|
2xx (queued or replayed) |
250 2.0.0 queued as <id> |
401 unauthorized |
535 5.7.8 Authentication credentials invalid |
429 rate_limited |
451 4.7.1 rate limited, retry later |
403 sending_paused |
451 4.7.1 sending paused |
403 forbidden_domain, 422 unknown_domain, any other 403 |
553 5.7.1 <code> |
422 invalid_from, 422 domain_not_verified |
553 5.6.0 <code> |
413, or a code that names a size |
552 5.3.4 <code> |
Any other 422 or 400 |
550 5.6.0 <code> |
5xx, or no answer |
451 4.4.1 Temporary system problem, try again later |
The full table, with the replies that the relay decides on its own, is on the SMTP relay page.