Errors, limits and pagination
Error codes and what to do about each, recovery by operation, pagination and limits.
Read as Markdown ↗Error format
Errors have this shape:
{"error":{"code":"not_found","message":"Resource not found."}}Handle errors by error.code; message explains the case, and its wording can change. Include the response's X-Request-ID when you report a problem.
| HTTP status | Meaning |
|---|---|
400 | The request is invalid. Correct it before sending it again. |
401 | The API key is missing, invalid or revoked; Replace an API key covers getting a new one. |
403 | The account can't perform this operation, the inbox's sending rules block a recipient, or a signup or redemption request carries another site's Origin. |
404 | The resource doesn't exist, was deleted or belongs to another account. A reply or forward source must also be in the sending inbox. |
409 | The request conflicts with current state: an unavailable address, the inbox limit, a source that can't be replied to, a draft change or submission, an idempotency key, or a webhook change in progress. |
413 | The request or message is too large. |
415 | Send Content-Type: application/json. |
429 | Too many signup or redemption requests, or not enough sending allowance. Wait for Retry-After when present. |
500, 503 | The service failed. A write may still have happened: recover by operation, below. |
Error codes
Handle a code not listed here by its HTTP status and the operation's recovery.
| Code | HTTP status | Meaning and recovery |
|---|---|---|
invalid_json | 400 | Send a valid UTF-8 JSON object, not an array or scalar. |
invalid_request | 400 | The signup or redemption body has an unsupported field. Signup takes an empty object; redemption takes phrase and, optionally, discovery_source and intended_use. |
invalid_claim | 400 | The phrase is invalid, expired or already used. Ask the human for a new one: they choose Get the message under Connect an agent at https://cherami.to/account. |
invalid_local_part | 400 | Use 1–64 letters, digits, hyphens or underscores for local_part, starting and ending with a letter or digit. |
invalid_inbox | 400 | Creation takes only local_part, name, sender_name and idempotency_key; an edit takes at least one of name and sender_name, and nothing else. |
invalid_name | 400 | Use plain text without control characters, at most 256 UTF-8 bytes after trimming. The message names the field. |
invalid_draft | 400 | Correct the draft field, source or listing parameter the message names. |
invalid_message | 400 | Correct the recipients, subject, body, in_reply_to, attachments or reply and forward fields as the message describes. |
invalid_idempotency_key | 400 | Use 1–128 ASCII letters, digits, hyphens or underscores. |
invalid_labels | 400 | Correct the label name, label change, filter group or discovery prefix the message names. |
invalid_message_ids | 400 | Supply 1–100 valid message IDs for a bulk label update. |
invalid_feedback | 400 | Send a nonempty plain-text body and, optionally, a nonempty single-line title, with no other fields. |
invalid_limit | 400 | Use an integer from 1 to 100. |
invalid_cursor | 400 | Send a cursor only to the listing, filters and order that returned it, or restart from the first page. |
invalid_search | 400 | Correct the search terms, filters, timestamps or ordering the message names. See search. |
invalid_webhook | 400 | Adding a webhook takes only url, event_types and inbox_ids. |
invalid_webhook_url | 400 | Use an https URL with a public hostname (not an IP address), without credentials or a fragment. |
invalid_webhook_event_types | 400 | Supply a nonempty array of message.received, message.sent or both. |
invalid_webhook_inbox_ids | 400 | Supply inbox_ids as an array of at most 10 inbox IDs, or an empty array for every inbox. |
unknown_webhook_inbox | 400 | An inbox_ids entry is not a live inbox of this account. Remove the IDs listed in error.details.inbox_ids. |
invalid_webhook_update | 400 | Supply at least one of event_types, inbox_ids and enabled, with enabled as a boolean. |
invalid_webhook_settings | 400 | The settings were refused, and nothing changed. Read the webhook, then send corrected values. |
invalid_webhook_rotation | 400 | Send an empty body or only a boolean immediate. |
invalid_webhook_headers | 400 | Correct the header change the message names. Names are HTTP tokens of at most 128 characters, other than Host, Content-Type, Content-Length, User-Agent and names starting svix- or webhook-; values are printable and at most 1024 characters; set and remove take at most 20 entries each. |
invalid_webhook_event | 400 | Use a Cherami event ID (a UUID): a delivered payload's id, or one listed by recent events. |
unauthorized | 401 | Send a valid API key as Authorization: Bearer <key>. If the key stopped working, get a new one through recovery. |
recipient_not_allowed | 403 | Nothing was sent: the inbox's sending rules block one or more recipients, which the message lists. Use allowed recipients, or ask the account owner to allow them; do not send through another inbox to get around the rules. |
operation_not_allowed | 403 | Sending, or deletion and restore, is turned off for this account; the message says which. Contact hello@cherami.to. |
origin_not_allowed | 403 | Signup and redemption refuse requests carrying another site's Origin header. Call them from the agent's runtime, not from a web page. |
not_found | 404 | The resource does not exist or does not belong to this account. A reply or forward source must also be in the sending inbox. |
address_unavailable | 409 | The address is in use, reserved or retired. Choose another local_part. |
inbox_limit_reached | 409 | All inbox slots are in use; error.details.allowance has the counts. Use an existing inbox, or request more slots rather than deleting an inbox still in use. |
reply_not_ready | 409 | The source cannot be replied to or forwarded yet. A received message must have processing_status ready; a sent message must have status accepted. |
reply_recipients_unavailable | 409 | No recipients remain once the sending inbox is excluded. Send a new message to recipients the task authorizes. |
reply_headers_unavailable | 409 | The target has no usable Message-ID to reply to. Send a new message without in_reply_to. |
draft_submitted | 409 | The draft's content froze on its first send attempt, so it cannot be edited. Read the sent message named by the draft's sent_message_id for the outcome; to send different content, create a new draft. |
draft_busy | 409 | Another change to this draft landed first. Read the draft, then repeat the edit or send if it still applies. |
draft_result_unavailable | 409 | The draft was already submitted and its sent message has since been deleted; nothing was sent again. If that message is still in Trash, restore it with the draft's sent_message_id to read the outcome. |
idempotency_conflict | 409 | The key was first used with different input. To recover that request, repeat it with its original inbox and payload; a different request needs its own key. |
idempotency_result_unavailable | 409 | The key already created an inbox, draft or send whose result has since been deleted. Nothing new was created or sent; do not repeat the request under a new key. If a sent message is still in Trash, restore it to read the outcome. |
webhook_operation_in_progress | 409 | Another change to this webhook is in progress. Retry in a minute. |
webhook_endpoint_missing | 409 | This webhook no longer receives events and cannot be changed. Delete it and add it again; the new webhook has a new ID and signing secret. |
body_too_large | 413 | Reduce the JSON request to the operation's body limit. |
message_too_large | 413 | The message exceeds 5 MiB counting text, HTML and base64 attachment content, or a forwarded original has more than 32 attachments. Reduce the content or attachments; for a forward, set include_attachments to false or attach selected files to a new message. |
unsupported_media_type | 415 | Send Content-Type: application/json. |
rate_limited | 429 | Too many signup or redemption requests from this network. Wait for Retry-After, then try again. |
outbound_limit_reached | 429 | The rolling 24-hour sending allowance cannot cover this message, at one charge per recipient. With reason temporary_exhaustion, retry at sufficient_capacity_at; with message_exceeds_allowance, the message needs more than the whole allowance: reduce its recipients or request a higher allowance. See sending allowance. |
internal_error | 500 | The operation failed. Retry a read; after a write the change may have happened, so recover as retry by operation describes. |
content_unavailable | 503 | Stored content could not be read. Retry later. |
outbound_unavailable | 503 | The request failed. If it was a send, reply or forward, its outcome is unknown: repeat it with the same idempotency key and unchanged payload within 24 hours of the first request; without a key, check sent messages before sending again. Retry a read. |
draft_unavailable | 503 | The draft request failed. Repeat a draft send with the same draft ID: it never sends twice. Repeat a creation with its original idempotency key and payload; without a key, list drafts first. Read the draft before repeating an edit. Retry a read or a deletion. |
feedback_unavailable | 503 | The feedback could not be confirmed and may already have been recorded. Repeat it only if a duplicate is acceptable. |
webhooks_unavailable | 503 | Webhook management failed. If it was a change, it may have been applied: list or read the webhooks before repeating it. |
webhook_provider_unavailable | 503 | The request could not be completed, and nothing changed. Retry later. |
webhook_creation_uncertain | 503 | The new webhook was not confirmed and is listed as pending. Within a few minutes the webhook in error.details.webhook_id becomes active, and you retrieve its secret, or leaves the list, and you add it again. |
webhook_secret_unavailable | 503 | The signing secret could not be read; retry the read later. When error.details.webhook_id is present the webhook was added: retrieve its secret rather than adding it again. |
webhook_rotation_uncertain | 503 | The rotation was not confirmed and finishes within a few minutes. Then read the webhook: a new secret_rotated_at means it rotated, and retrieving the secret gives you the new one. |
webhook_settings_uncertain | 503 | The settings change was not confirmed. Apply the same values again; repeating them is safe. |
webhook_headers_uncertain | 503 | The header change was not confirmed. Apply the same change again; repeating it is safe. |
webhook_headers_unavailable | 503 | Header names could not be read. When error.details.applied is true the change was made; list the headers again. |
webhook_history_unavailable | 503 | Delivery history could not be read. Retry later. |
A send the provider refused is not an error here: it returns a sent message with status: "rejected" and an error_code such as E_RECIPIENT_SUPPRESSED. See sending outcomes.
Retry by operation
After a 5xx, a timeout or a lost response, a write may have completed. Recover by operation:
| Operation | After an uncertain response |
|---|---|
| Read | Retry after a short wait. |
| Create an inbox or draft | Repeat with the original key and payload within 24 hours. Without a key, look for it in the listing before creating again. See inbox recovery and draft recovery. |
| Send, reply or forward | Repeat with the original key and unchanged payload within 24 hours. Without a key, check sent messages instead of sending again. |
| Send a draft | Repeat the same draft send at any time; it never sends twice. |
| Edit inbox names or a draft | Read the current state, then repeat the change if it's still needed. |
| Change labels | Repeat the change; labels are never duplicated. |
| Delete | Repeat the request; it returns 202 or 404. |
| Restore from Trash | Repeat the request; status: "already_live" means the message is already back. |
| Redeem an approval phrase | Ask your human for a new approval (recovery). |
| Feedback | Repeating creates a duplicate; send again only if that's acceptable. |
| Add a webhook | List webhooks, find the URL and retrieve its secret rather than adding it again. |
| Edit a webhook or its headers | Apply the same values again; they're absolute. |
| Rotate a webhook secret | Read the webhook first: a new secret_rotated_at means the rotation happened. |
| Delete a webhook | Repeat the request; pending removal finishes in the background. |
Pagination
Listings of received and sent messages, drafts, labels, conversations, a conversation's messages, Trash, webhook events and delivery attempts take limit (1–100, default 20) and return next_cursor. Send next_cursor back, URL-encoded, as cursor with the same URL, filters and order, and stop when it is null:
GET /v1/inboxes/INBOX_ID/messages?limit=20&cursor=URL_ENCODED_CURSOR HTTP/1.1
Host: cherami.to
Authorization: Bearer YOUR_CREDENTIALPages are live: in the default newest-first order, new mail appears on the first page, so start again from there to find it. Receive and poll for mail covers polling.
Limits
An account's allowances are in inbox_allowance from List inboxes and in Read sending allowance; Get help or request more allowance covers getting more. These technical limits apply to every account:
| Resource | Limit |
|---|---|
| Incoming email | 25 MiB, including encoded attachments. |
| Outgoing email | 5 MiB, counting text, HTML and base64 attachments. |
| Send and draft request JSON | 8 MiB. |
| Recipients per message | 50 across To, Cc and Bcc. |
| Attachments per message | 32. |
| Subject | 998 UTF-8 bytes, without control characters. |
| Feedback request JSON | 20 KiB. |
| Label-update JSON | 20 KiB for one message or a conversation; 32 KiB for a bulk update. |
| Bulk label targets | 1–100 message IDs per request. |
| Label names | 1–128 UTF-8 bytes after trimming, without control characters. |
| Labels per request array | 32 in each add or remove array, a send's initial labels, or a filter group. |
| Other JSON requests | 4 KiB. |
| Signup discovery | 10 requests per IP per hour. |
| Agent approvals in Account | 10 per IP per 15 minutes. |
| Phrase redemption | 10 attempts per IP per 15 minutes. |
| Approval phrase | One use, within 6 hours. |
| Webhook inbox scope | 10 inboxes per webhook; empty means every inbox. |
| Webhook custom headers | 20 set and 20 removed per request; values at most 1024 characters. |