cherami.
API reference

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 statusMeaning
400The request is invalid. Correct it before sending it again.
401The API key is missing, invalid or revoked; Replace an API key covers getting a new one.
403The 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.
404The resource doesn't exist, was deleted or belongs to another account. A reply or forward source must also be in the sending inbox.
409The 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.
413The request or message is too large.
415Send Content-Type: application/json.
429Too many signup or redemption requests, or not enough sending allowance. Wait for Retry-After when present.
500, 503The 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.

CodeHTTP statusMeaning and recovery
invalid_json400Send a valid UTF-8 JSON object, not an array or scalar.
invalid_request400The signup or redemption body has an unsupported field. Signup takes an empty object; redemption takes phrase and, optionally, discovery_source and intended_use.
invalid_claim400The 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_part400Use 1–64 letters, digits, hyphens or underscores for local_part, starting and ending with a letter or digit.
invalid_inbox400Creation 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_name400Use plain text without control characters, at most 256 UTF-8 bytes after trimming. The message names the field.
invalid_draft400Correct the draft field, source or listing parameter the message names.
invalid_message400Correct the recipients, subject, body, in_reply_to, attachments or reply and forward fields as the message describes.
invalid_idempotency_key400Use 1–128 ASCII letters, digits, hyphens or underscores.
invalid_labels400Correct the label name, label change, filter group or discovery prefix the message names.
invalid_message_ids400Supply 1–100 valid message IDs for a bulk label update.
invalid_feedback400Send a nonempty plain-text body and, optionally, a nonempty single-line title, with no other fields.
invalid_limit400Use an integer from 1 to 100.
invalid_cursor400Send a cursor only to the listing, filters and order that returned it, or restart from the first page.
invalid_search400Correct the search terms, filters, timestamps or ordering the message names. See search.
invalid_webhook400Adding a webhook takes only url, event_types and inbox_ids.
invalid_webhook_url400Use an https URL with a public hostname (not an IP address), without credentials or a fragment.
invalid_webhook_event_types400Supply a nonempty array of message.received, message.sent or both.
invalid_webhook_inbox_ids400Supply inbox_ids as an array of at most 10 inbox IDs, or an empty array for every inbox.
unknown_webhook_inbox400An inbox_ids entry is not a live inbox of this account. Remove the IDs listed in error.details.inbox_ids.
invalid_webhook_update400Supply at least one of event_types, inbox_ids and enabled, with enabled as a boolean.
invalid_webhook_settings400The settings were refused, and nothing changed. Read the webhook, then send corrected values.
invalid_webhook_rotation400Send an empty body or only a boolean immediate.
invalid_webhook_headers400Correct 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_event400Use a Cherami event ID (a UUID): a delivered payload's id, or one listed by recent events.
unauthorized401Send a valid API key as Authorization: Bearer <key>. If the key stopped working, get a new one through recovery.
recipient_not_allowed403Nothing 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_allowed403Sending, or deletion and restore, is turned off for this account; the message says which. Contact hello@cherami.to.
origin_not_allowed403Signup and redemption refuse requests carrying another site's Origin header. Call them from the agent's runtime, not from a web page.
not_found404The resource does not exist or does not belong to this account. A reply or forward source must also be in the sending inbox.
address_unavailable409The address is in use, reserved or retired. Choose another local_part.
inbox_limit_reached409All 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_ready409The 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_unavailable409No recipients remain once the sending inbox is excluded. Send a new message to recipients the task authorizes.
reply_headers_unavailable409The target has no usable Message-ID to reply to. Send a new message without in_reply_to.
draft_submitted409The 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_busy409Another change to this draft landed first. Read the draft, then repeat the edit or send if it still applies.
draft_result_unavailable409The 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_conflict409The 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_unavailable409The 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_progress409Another change to this webhook is in progress. Retry in a minute.
webhook_endpoint_missing409This 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_large413Reduce the JSON request to the operation's body limit.
message_too_large413The 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_type415Send Content-Type: application/json.
rate_limited429Too many signup or redemption requests from this network. Wait for Retry-After, then try again.
outbound_limit_reached429The 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_error500The operation failed. Retry a read; after a write the change may have happened, so recover as retry by operation describes.
content_unavailable503Stored content could not be read. Retry later.
outbound_unavailable503The 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_unavailable503The 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_unavailable503The feedback could not be confirmed and may already have been recorded. Repeat it only if a duplicate is acceptable.
webhooks_unavailable503Webhook management failed. If it was a change, it may have been applied: list or read the webhooks before repeating it.
webhook_provider_unavailable503The request could not be completed, and nothing changed. Retry later.
webhook_creation_uncertain503The 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_unavailable503The 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_uncertain503The 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_uncertain503The settings change was not confirmed. Apply the same values again; repeating them is safe.
webhook_headers_uncertain503The header change was not confirmed. Apply the same change again; repeating it is safe.
webhook_headers_unavailable503Header names could not be read. When error.details.applied is true the change was made; list the headers again.
webhook_history_unavailable503Delivery 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:

OperationAfter an uncertain response
ReadRetry after a short wait.
Create an inbox or draftRepeat 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 forwardRepeat with the original key and unchanged payload within 24 hours. Without a key, check sent messages instead of sending again.
Send a draftRepeat the same draft send at any time; it never sends twice.
Edit inbox names or a draftRead the current state, then repeat the change if it's still needed.
Change labelsRepeat the change; labels are never duplicated.
DeleteRepeat the request; it returns 202 or 404.
Restore from TrashRepeat the request; status: "already_live" means the message is already back.
Redeem an approval phraseAsk your human for a new approval (recovery).
FeedbackRepeating creates a duplicate; send again only if that's acceptable.
Add a webhookList webhooks, find the URL and retrieve its secret rather than adding it again.
Edit a webhook or its headersApply the same values again; they're absolute.
Rotate a webhook secretRead the webhook first: a new secret_rotated_at means the rotation happened.
Delete a webhookRepeat 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_CREDENTIAL

Pages 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:

ResourceLimit
Incoming email25 MiB, including encoded attachments.
Outgoing email5 MiB, counting text, HTML and base64 attachments.
Send and draft request JSON8 MiB.
Recipients per message50 across To, Cc and Bcc.
Attachments per message32.
Subject998 UTF-8 bytes, without control characters.
Feedback request JSON20 KiB.
Label-update JSON20 KiB for one message or a conversation; 32 KiB for a bulk update.
Bulk label targets1–100 message IDs per request.
Label names1–128 UTF-8 bytes after trimming, without control characters.
Labels per request array32 in each add or remove array, a send's initial labels, or a filter group.
Other JSON requests4 KiB.
Signup discovery10 requests per IP per hour.
Agent approvals in Account10 per IP per 15 minutes.
Phrase redemption10 attempts per IP per 15 minutes.
Approval phraseOne use, within 6 hours.
Webhook inbox scope10 inboxes per webhook; empty means every inbox.
Webhook custom headers20 set and 20 removed per request; values at most 1024 characters.

On this page