# Error catalogue

71 codes. Every 4xx and 5xx carries `code`, `message`, `fix` and `docs_url`; only a refusal that waiting can cure carries `retry_after_seconds`.

| Code | Status | Retryable | Message | Fix |
| --- | --- | --- | --- | --- |
| `validation_error` | 400 | no | Error in one or more fields. | Correct the fields listed in the error details and retry the request. |
| `invalid_idempotency_key` | 400 | no | Idempotency-Key must be 1-256 characters. | Send a non-empty Idempotency-Key header of at most 256 characters. |
| `missing_required_field` | 422 | no | Body is missing required fields. | Include from, to and subject in POST /emails. |
| `invalid_from_address` | 422 | no | The from field is not a valid address. | Use a plain address or "Name <addr@domain>" format in POST /emails. |
| `invalid_parameter` | 422 | no | A parameter has an invalid value. | Correct the named parameter and retry. |
| `invalid_cursor` | 400 | no | This page cursor is no longer valid. | Call the list again without a cursor, or pass the next_cursor from a page you still have. |
| `invalid_attachment` | 422 | no | Each attachment needs content (base64) or path. | Provide attachment.content or attachment.path in POST /emails. |
| `invalid_region` | 422 | no | Region must be one of us, eu. | Pass region as us (Oregon) or eu (Helsinki) in POST /domains. Omitting it stores us. For this release us-east-1, sa-east-1 and ap-northeast-1 map to us, and eu-west-1 maps to eu. |
| `tracking_subdomain_unverified` | 422 | no | Click and open tracking need a verified tracking host on this domain. Links were sent as written. | Publish the tracking CNAME shown by GET /domains/:id, then POST /domains/:id/verify. Tracking starts on the next send. |
| `tracking_subdomain_cannot_be_removed` | 422 | no | The tracking subdomain can be changed, never removed. | Pass a new label such as "clicks" in PATCH /domains/:id. To stop counting opens and clicks, turn those switches off instead. |
| `domain_field_immutable` | 422 | no | Name, region, and return-path cannot change on an existing domain. | Add a new domain with POST /domains. PATCH /domains/:id accepts click_tracking, open_tracking, and tracking_subdomain only. |
| `open_tracking_on_transactional` | 422 | no | Open tracking on transactional mail can make inbox providers treat receipts and alerts as marketing. | Leave open tracking off for receipts, password resets, and alerts. Turn it on for broadcasts if you want open counts. |
| `domain_already_exists` | 409 | no | That domain is already registered on this account. | Use the existing domain from GET /domains, or remove it with DELETE /domains/:id first. |
| `domain_verified_elsewhere` | 409 | no | This domain is already verified on another account. | Contact support to move the domain. We cannot verify it here while another account already has it verified. |
| `domain_not_verified` | 403 | no | Domain is not verified for sending. | Add the DNS records shown by GET /domains/:id, then call POST /domains/:id/verify. |
| `service_unavailable` | 503 | no | The API is temporarily unavailable — a dependency failed transiently. | Retry after the seconds given in Retry-After. If it persists beyond a few minutes, check the public status page (GET /status) for the affected component. |
| `account_suspended` | 403 | no | This account is suspended. | Suspensions follow the published enforcement policy (docs/TRUST-SAFETY). Contact support to appeal; unused prepaid balance is refunded on termination. |
| `account_sandboxed` | 403 | no | This account is in the sandbox and may only send to its own verified domains. | Verify the recipient domain with POST /domains + POST /domains/:id/verify, or file POST /trust/appeal for a person to review this account. |
| `trust_throttled` | 429 | no | This account is being throttled by the trust ladder. | Send volume is temporarily capped because deliverability metrics crossed a published threshold. Check GET /trust/standing for the metric and value, file POST /trust/appeal if this is unexpected. |
| `suppressed_recipient` | 422 | no | Recipient is on the account suppression list. | GET /suppressions says which address and why. A hard bounce you have fixed can be cleared with DELETE /suppressions/:id; an unsubscribe or a spam complaint cannot — that address asked not to be contacted. |
| `missing_api_key` | 401 | no | Missing API key in authorization header. | Sign in at /login so the console sends its session cookie, or create a key with POST /api-keys and send "Authorization: Bearer as_...". |
| `session_required` | 401 | no | You are signed out. | Sign in to the console, then retry. API keys cannot call this route. |
| `human_action_required` | 403 | no | This is a person’s decision, so an API key cannot make it. | Ask whoever runs this account to do it in the console. Scoping the key differently does not change the answer, and retrying fails the same way. |
| `csrf_origin_rejected` | 403 | no | This request came from a page on another site, and it changes data. | Call the API with an API key (Authorization: Bearer …) instead of a session cookie, or make the request from the console. Create a key in the console under Settings, API keys. |
| `mfa_required` | 401 | no | This session has not completed two-factor authentication. | Finish signing in at /verify with a code from your authenticator app, or one of your recovery codes. Manage the second factor in the console under Settings, Security. |
| `billing_not_configured` | 503 | no | Billing is not configured on this deployment. | The operator must set STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET (docs/STRIPE.md §4). Until then nothing can be bought; GET /billing/plan still answers and the 14-day Pro trial still starts. |
| `plan_not_purchasable` | 400 | no | This tier cannot be bought through checkout. | Pass one of the tier ids listed under `plans` by GET /billing/plan (starter, pro, scale) to POST /billing/checkout. |
| `plan_required` | 402 | no | This account has no plan in force, so real email is not sent. | A person on this account chooses a plan, or starts the 14-day Pro trial, in the console under Settings → Billing. Simulation sends keep working meanwhile. |
| `trial_already_used` | 409 | no | This account has already used its 14-day Pro trial. | Choose a plan in the console under Settings → Billing; POST /billing/checkout starts the payment. |
| `plan_already_active` | 409 | no | This account already has a plan in force. | Nothing to start. GET /billing/plan shows the plan, and plan changes are made in the console under Settings → Billing. |
| `term_not_on_sale` | 409 | no | That billing term is not available to buy yet. | Pass one of the terms listed under `terms_on_sale` by GET /billing/plan (monthly and yearly) to POST /billing/checkout. |
| `subscription_active` | 409 | no | This account already has a live subscription. | Change plans with POST /billing/portal — the Stripe customer portal prorates the change and shows the amount before it is confirmed. Checkout is only for an account with no subscription. |
| `billing_customer_missing` | 404 | no | This account has no billing customer yet. | Start a subscription with POST /billing/checkout first; the customer portal only exists once a checkout has run. |
| `stripe_signature_invalid` | 400 | no | The Stripe-Signature header did not verify against this endpoint’s secret. | Only Stripe calls POST /webhooks/stripe. If you are Stripe: the endpoint secret configured as STRIPE_WEBHOOK_SECRET must be the whsec_ of THIS endpoint in THIS mode (test and live differ), and the body must be delivered unmodified. |
| `invalid_api_key` | 403 | no | API key is invalid or revoked. | Create a new key with POST /api-keys; deleted keys cannot be restored. |
| `restricted_api_key` | 403 | no | This API key is restricted to sending only. | Use a full_access key (POST /api-keys with permission=full_access) for management endpoints. |
| `insufficient_role` | 403 | no | Your role on this account cannot make this change. | Ask an owner to make the change, or to raise your role with PATCH /team/members/:id. |
| `last_owner_required` | 422 | no | An account must keep at least one owner. | Promote another member to owner with PATCH /team/members/:id first, then retry. |
| `seat_limit_reached` | 422 | no | This plan has no seat left for another member. | Remove a member with DELETE /team/members/:id, cancel a pending invite with DELETE /team/invites/:id, or move to a plan with more seats. Seats are never billed per seat. |
| `invite_not_valid` | 422 | no | This invitation is expired, cancelled, or already accepted. | Ask an owner or admin to send a new one with POST /team/invites. |
| `invite_email_mismatch` | 403 | no | This invitation was sent to a different address than the one you are signed in as. | Sign out, sign in as the invited address, then open the invitation link again — POST /invite/:token/accept binds the membership to the signed-in address. |
| `already_in_account` | 409 | no | This address already belongs to an account. | Leave the current account first, or ask the inviter to send the invitation to an address that has no account. |
| `domain_scope_violation` | 403 | no | This API key may only send from its scoped domain. | Send from the scoped domain, or create a key without a domain scope via POST /api-keys. |
| `not_found` | 404 | no | Endpoint or resource does not exist. | Check the path against GET /openapi.json and the resource id against your account. |
| `session_expired` | 404 | no | This connection to AgentiSend has expired, so nothing was run. Reconnect your AI client and try again. | Reconnect your AI client so it opens a new connection, then retry the request. |
| `payload_too_large` | 413 | no | Request body is larger than this endpoint accepts. | Send a smaller body. Limits are per endpoint — /mcp accepts 1 MB per JSON-RPC call, and attachments belong on POST /emails, which accepts 40 MB after base64. |
| `unsupported_media_type` | 415 | no | This endpoint does not accept that content type. | Send the body as JSON with `Content-Type: application/json`. |
| `method_not_allowed` | 405 | no | Method not allowed for this path. | Use a method listed in Allow for this route. |
| `idempotency_in_flight` | 409 | after 5s | A request with this Idempotency-Key is still in progress. | Wait and retry with the same Idempotency-Key to receive the original response. |
| `idempotency_payload_mismatch` | 409 | no | Same Idempotency-Key was used with a different payload. | Reuse the exact same body for retries, or send a new Idempotency-Key for a new request. |
| `agent_budget_exceeded` | 402 | no | Key budget for the current period is exhausted. | Wait for the period to reset — get_agent_budget and GET /limits/keys/:id both say when. Raising a budget is a person’s decision, made in the console; a key cannot raise its own. |
| `plan_limit_reached` | 402 | no | This account has used every email included in its plan for the current period. | Upgrade in Settings → Billing, or wait until the reset date in this error. |
| `key_budget_exceeds_plan` | 422 | no | A key budget cannot be unlimited or larger than the emails this plan includes per month. | Set a whole number at or below the plan inclusion, or upgrade in Settings → Billing. |
| `domain_limit_reached` | 422 | no | This plan has no sending domain left. | Delete a domain with DELETE /domains/:id, or upgrade in Settings → Billing. |
| `rate_ceiling_exceeded` | 429 | after 60s | Per-minute rate ceiling for this key is exhausted. | Wait the seconds below and send the same request again. Raising the ceiling is a person’s decision, made in the console; a key cannot raise its own. |
| `daily_quota_exceeded` | 429 | no | Account daily quota reached. | Upgrade the plan or wait for the UTC reset; see GET /usage for what this account has spent. |
| `monthly_quota_exceeded` | 429 | no | Account monthly quota reached. | Upgrade the plan or wait for the cycle reset; see GET /usage for what this account has spent. |
| `rate_limiter_unavailable` | 503 | after 5s | The rate limiter could not be reached, so nothing was run. | Retry in a few seconds. Nothing was sent and nothing was changed — writes are refused rather than run unmetered against a shared sending reputation. |
| `rate_limit_exceeded` | 429 | after 60s | Too many requests. | Back off and retry honoring the Retry-After header. |
| `approval_required` | 403 | no | This action requires human approval before it executes. | It is waiting in the console approvals inbox; GET /agent-actions shows it and what it says. A person decides — the key that asked cannot approve itself. |
| `trust_paused` | 403 | no | Sending is paused by the trust system. | Review reasons via GET /trust/standing, then file an appeal via POST /trust/appeal. |
| `kill_switch_active` | 403 | no | This API key has been paused with the kill switch. | Read GET /trust/standing for why it was paused. Only a person signed in to the console can resume it; the paused key cannot resume itself. |
| `internal_server_error` | 500 | no | Unexpected error. | Retry ONCE after a short pause, with the same Idempotency-Key so the retry cannot double-send. If it fails again, stop retrying and report the x-request-id from the response — that id is what identifies this exact failure in support. |
| `support_ticket_not_found` | 404 | no | That request does not exist. | Open Support in the console and pick a request from the list, or start a new one. |
| `support_closed` | 409 | no | This request is closed, so a reply cannot be added to it. | Start a new request from Support in the console. If this one was resolved in the last 14 days, reopen it first. |
| `support_reopen_expired` | 409 | no | This request can no longer be reopened. | A request can be reopened within 14 days of being marked resolved. Start a new request from Support in the console. |
| `support_merge_conflict` | 409 | no | These requests belong to different accounts and cannot be merged. | Merge only requests from the same account. |
| `support_upload_rejected` | 400 | no | That file cannot be attached. | Attach a screenshot, PDF, CSV or log file up to 10 MB. Up to 5 files, 25 MB in total, per message. |
| `support_upload_too_large` | 413 | no | That file is larger than 10 MB, or the files together are larger than 25 MB. | Remove a file or send a smaller one. Each file must be 10 MB or smaller, and the message 25 MB in total. |
| `support_attachment_expired` | 410 | no | This file has expired. Attachments are kept for 30 days. | Ask the person who uploaded it to send the file again on the request. |
| `support_rate_limited` | 429 | after 3600s | Too many support requests from this account just now. | Wait and try again. You can open 5 new requests an hour and send 30 replies an hour. |

---

Source: https://agentisend.com/docs/errors — the HTML at that URL and this file are one document, generated from one source.
