Errors — the error envelope and every code
Every failed request returns the same shape, with a non-2xx status. There is one envelope to parse, always:
{
"error": {
"type": "invalid_request_error",
"code": "missing_field",
"message": "Subject is required.",
"param": "subject",
"request_id": "req_01KVF46QACE5C361SBYTP6JE0S"
}
}type— the broad category; it usually determines the HTTP status.code— the specific, stable, machine-readable reason. Branch on this.message— a human-readable explanation. Do not parse it; it can change.param— the offending field for validation errors, otherwisenull.request_id— also returned as theX-Request-Idheader. Quote it to support.resets_atandretry_after_seconds— on every 429: when the request can succeed (UTC) and how many seconds away that is. Both arenullwhen waiting will not help, for a cap that only an upgrade or a deletion lifts.
Type → HTTP status
The type usually maps to the status, so you can handle whole classes at once:
invalid_request_error→ 400 — malformed or invalid input. Some of its codes answer another status, such asduplicate_resourceandagent_name_conflict(409),body_too_large(413) andunknown_test_address(422).authentication_error→ 401 — missing, invalid, revoked, or expired key.permission_error→ 403 — the key lacks the scope, or the workspace is not approved to send.payment_error→ 402 — payment required, subscription canceled, or a gated feature.resource_error→ 404 — the referenced object does not exist.abuse_error→ 422 — the request is well-formed but disallowed (suppressed recipient, paused agent).rate_limit_error→ 429 — a limit was hit; retry at the body’sresets_at.api_error→ 500 / 502 / 503 — an internal error, an upstream provider failure, or a service briefly unavailable.
Common codes
The codes you will branch on most:
- Auth (401):
missing_authorization,invalid_api_key,revoked_api_key,expired_api_key. - Scope & approval (403):
insufficient_scope(a send or a draft that would create an agent needsmanage),workspace_not_approved. - Connected apps (403):
connected_app_not_allowed— an app connected by signing in asked for something no such app may do (see Connected apps). - Test mode (403):
live_mode_required— test mode asked for something only live mode may do: trash or delete a thread that holds live mail, assign or unassign one, add or delete a note on one, change, send or delete a draft a live key made, reschedule or cancel a live scheduled send, or run the forwarding test, which sends a real email. A test key, or a dashboard session sendingX-Mails-Mode: test, also gets it when it revokes a live key, or changes, pauses or archives an agent that has a live key tied to it, live mail (sent, scheduled or received) or a draft a live key made; do it with a live key or from the dashboard. The reverse istest_mode_required: a live key replying to test mail, or sending or scheduling a draft that answers it, which would mail an address a test made up. - Validation (400/422):
missing_field,invalid_field,invalid_param_value(a value too long, too short, malformed or not one of those allowed) — checkparam. - Send-time (404/422):
agent_not_found,agent_paused,agent_archived,recipient_suppressed,recipient_not_allowed(a recipient outside the agent’sallowlist_domainsor on itsblocklist_domains), andunknown_test_address(422) for an address attest.mails.aiother than the test address. - Limits (429):
hourly_limit_exceeded,daily_limit_exceeded,monthly_limit_exceeded,free_tier_exceeded. - Billing (402):
payment_required,subscription_canceled,feature_not_enabled,resource_cap_exceeded(a plan’s cap on agents, API keys, webhooks, open drafts or the files drafts hold). - Idempotency (400):
invalid_idempotency_key— a key used again with a different body, or on a different route; use a new key. - Conflict (409):
duplicate_resource— an idempotency key or a draft already in flight. - Agent name (409):
agent_name_conflict— two live agents share the name a request used; use the agent’sagt_id. - Upstream (502/503):
upstream_error(usually 502) — the email service did not confirm it took the mail, so it is recorded as not sent, and the message names a request id to quote to support;upstream_unavailable(503) — it is not taking more mail right now and nothing was sent, so send it again afterRetry-After.
duplicate_resource also answers a change that came too late: editing or deleting a draft, or rescheduling or canceling a scheduled message, after it started sending, and deleting a thread for good while one of its messages is being sent. Read the resource again before you retry. It also answers an If-Match on PATCH /v1/drafts/{id}, DELETE /v1/drafts/{id} or POST /v1/drafts/{id}/send that no longer matches, with param updated_at. A send gets the same answer, header or no header, when a change or a file reaches the draft just as the send begins. A chunk sent to a draft’s file that has finished uploading gets it with param index, and changes nothing.
invalid_field can be a 409 too: sending a draft with a file still uploading, or with a forwarded file that can no longer be read, answers it with param attachments.
An agent named by its name resolves to the calling key’s own agent first, then to a live agent over an archived one. Only the agent’s own routes (reading, changing or archiving it, its forwarding test, its inbox), an agent_id filter, reputation and POST /v1/test/inbound answer agent_name_conflict when two agents still match; a send keeps using the oldest of them. A key tied to one agent never gets that 409, which would name other agents to it. To such a key, a shared name that is not its own agent’s answers as any other agent does: 404 from the agent’s own routes, and 403 from a filter, reputation and POST /v1/test/inbound.
Handling errors well
- Branch on
code, notmessage. Codes are stable; messages are for humans and may be reworded. - Retry a 429 at
resets_at. The body says when the request can succeed. ARetry-Afterheader is sent only when that is 60 seconds or less away; an hourly, daily or monthly cap usually means a longer wait and sends none. Back off rather than hammering. - Retry only the safe ones.
upstream_unavailable(503, after itsRetry-After) and 500s are retryable with exponential backoff; pair retries with anIdempotency-Keyso a retried send never duplicates. - Log the
request_id. It is the fastest way for support to trace exactly what happened on a given call.
Every code
The code field takes one of these 68 values, the list in the API’s OpenAPI document: invalid_field, missing_field, missing_param, invalid_param_value, method_not_allowed, invalid_idempotency_key, duplicate_resource, slug_invalid, slug_reserved, slug_taken, agent_name_conflict, body_too_large, missing_authorization, missing_api_key, invalid_api_key, expired_api_key, revoked_api_key, invalid_access_token, email_not_verified, insufficient_scope, connected_app_not_allowed, sso_required, workspace_mismatch, agent_not_owned, rate_limit_exceeded, daily_limit_exceeded, hourly_limit_exceeded, monthly_limit_exceeded, agent_not_found, agent_paused, agent_archived, resource_not_found, recipient_suppressed, recipient_not_allowed, assignee_not_member, domain_not_verified, ses_role_not_assumable, ses_role_external_id_not_required, ses_permission_denied, ses_setup_failed, ses_message_rejected, ses_events_not_confirmed, payment_required, payment_method_failed, subscription_canceled, billing_state_inconsistent, free_tier_exceeded, plan_limit_exceeded, cold_email_prohibited, agent_suspended, new_workspace_fanout_exceeded, resource_cap_exceeded, workspace_cap_exceeded, feature_not_enabled, test_mode_required, live_mode_required, unknown_test_address, workspace_not_approved, spam_pattern_detected, classifier_rejected, high_injection_score, complaint_threshold_exceeded, bounce_threshold_exceeded, internal_error, internal_server_error, upstream_error, upstream_unavailable and service_unavailable.
Was this page helpful?