Errors and troubleshooting

Errors are JSON with a name, a machine-readable code, a message and often a fix. Match on code, not on message.

{ "name": "MessageRejectedError", "code": "inbox_paused", "message": "Inbox is paused and cannot send", "fix": "Set the inbox status to active" }

Validation errors (400, code validation_error) add errors: a list of { path, message }. Plan limit errors (402) add errors[0] with limit, current, max and the plan that allows more. Every response carries an x-request-id header; quote it when you ask for help. 429 responses carry Retry-After.

Every error code#

The list below is checked against the server's source on every test run, so a code cannot be added without being documented here.

Errors from /v0 and the hosted MCP endpoint

StatusCodeCauseFix
400bad_requestA malformed request that is not a schema failure: invalid JSON, an unsupported content type, a body over the route's limit, a malformed URL, or a field the route does not allow (for example reason on an allow list).Send valid JSON with Content-Type: application/json and stay inside the documented limits.
400invalid_webhook_urlThe webhook URL is not a public https URL, or it resolves to a private or internal address.Use a public https endpoint.
400validation_errorThe body or query failed validation. The response carries an errors list of { path, message }. Also returned for metrics ranges that start more than 90 days back or need more than 1000 buckets, and for a wrong agent verification code.Read errors[].path and fix that field.
401unauthorizedNo API key, an unknown key, or a revoked key. The same code is used for an invalid provider signature on operator ingest routes.Send Authorization: Bearer <api_key>. Create a new key if the old one was deleted.
402plan_limit_exceededThe action would exceed your plan: inboxes, custom domains, webhooks, monthly sends or the Free daily sends. The body names the limit, the current value and the plan that allows more.Delete something, wait for the period to roll over, or upgrade with POST /v0/billing/checkout.
403already_existsThe inbox address, list entry or domain already exists. Agent sign-up returns the same code with status 409 for a taken username.Pick a different value, or read the existing resource. Use client_id to make creates idempotent.
403email_unverifiedThe owner email has not been verified, so sending and creating API keys are locked.Click the link in the verification email, or call POST /auth/resend-verification from the dashboard session.
403inbox_pausedThe inbox status is paused. Sends and scheduled drafts from it fail.PATCH /v0/inboxes/:inbox_id with { "status": "active" }.
403message_rejectedThe mail provider refused the message (for example an invalid recipient).Read the message for the provider's reason, correct the message and send again.
403missing_permissionThe key does not have the permission the route needs. The message names it.Create a key with that permission, or use an organization key.
403pod_protectedThe default pod cannot be deleted.Delete the other pods, not the default one.
403recipient_blockedA send block list matched a recipient, or a non-empty send allow list did not include it.Remove the entry from the list or send to an allowed recipient.
403send_lockedAn agent-created account has no verified human yet.Call POST /v0/agent/attach-human, then submit the emailed code to POST /v0/agent/verify.
404not_foundNo such resource, or it is outside your key's scope (the API does not reveal which). Also returned for an unknown route.Check the id, the inbox in the path and the key's pod or inbox scope.
409already_subscribedThe organization already has an active subscription.Change plan with POST /v0/billing/portal.
409already_verifiedThe agent's human has already verified.Nothing to do.
409draft_sendingThe draft is being sent right now, so it cannot be edited or deleted.Retry after a moment, or read the draft's send_status.
409human_replacement_limitThe human on an agent organization can be replaced at most twice.Verify the current human instead.
409idempotency_key_reuseThe same Idempotency-Key was used on a different path.Use a fresh key per logical operation.
409no_subscriptionThere is no active subscription to cancel or manage.Start one with POST /v0/billing/checkout.
409not_an_agent_organizationThe route is only for organizations created through agent sign-up.Use the dashboard or an organization that signed up as an agent.
409pod_not_emptyThe pod still has inboxes.Delete the pod's inboxes first.
413payload_too_largeInline attachment content is over 6 MB combined, or a URL attachment is over 10 MB (25 MB total).Attach files by url and keep each file under 10 MB.
422domain_not_verifiedAn inbox was requested on a domain that has not been verified.Publish the DNS records and call POST /v0/domains/:domain_id/verify.
422html_requiredtrack_opens needs an html body.Add html.
422range_too_largeA rates query would scan more than 200,000 events.Narrow start and end or raise period.
422recipient_suppressedAn earlier hard bounce or complaint suppressed the address.Do not mail it. If you are sure it is valid again, DELETE /v0/suppressions/:address.
422reserved_usernameThe username is reserved (for example admin or postmaster).Choose another username.
422subdomains_not_enabledAn inbox was requested on a subdomain of a domain that does not have subdomains_enabled.Enable subdomains on the parent domain, or add the subdomain as its own domain.
422too_many_recipientsMore than 50 unique recipients across to, cc and bcc.Split the send.
422tracking_disabledtrack_opens was requested but the operator disabled open tracking.Send without track_opens.
429rate_limitedToo many requests from your IP or key, the daily per-inbox send cap (100 per rolling 24 hours by default), or too many verification emails to one address. Check Retry-After.Wait and retry; back off exponentially.
429too_many_attemptsToo many wrong verification codes or login attempts.Wait for the Retry-After period.
500internal_errorAn unexpected server error.Retry with the same Idempotency-Key; contact support with the x-request-id header if it persists.
502billing_provider_errorThe payment provider returned an error when starting checkout, the portal or a cancel.Retry; the message carries the provider's text.
503billing_not_configuredThe payment provider is not configured on this server.Choose the other provider, or contact the operator.

Errors from the dashboard's session endpoints (/auth)

StatusCodeCauseFix
4002fa_not_set_upEnable was called before setup.Call setup first.
400invalid_codeThe code entered while enabling or disabling two-step verification is wrong.Use a current authenticator code.
400invalid_tokenAn email verification, password reset or invitation link is invalid, used or expired.Request a new one.
401invalid_credentialsWrong email, password or current password.Check the credentials, or reset the password.
401invalid_totpThe two-step code is wrong.Use a current code or an unused recovery code.
401totp_requiredThe account has two-step verification on and no code was sent.Send totp_code (an authenticator code or a recovery code).
403csrf_failedA dashboard session request lacked a valid CSRF token or same-origin header. API-key requests are not affected.Send x-csrf-token from GET /auth/me and a same-origin Origin.
403email_not_verifiedYour own email is unverified, so you cannot invite members.Verify your email first.
403forbiddenOnly the organization owner can invite members.Ask the owner.
4092fa_already_enabledTwo-step verification is already on.Nothing to do.
409already_memberThe invited email is already a member.Nothing to do.

Errors on operator-only routes; you will not normally see these

StatusCodeCauseFix
400invalid_signatureA payment provider webhook failed signature verification.Check the webhook signing secret.
502upstream_errorFetching a received email from the provider failed, so the provider will retry.Check provider status and the API key.
503not_configuredAn operator ingest route is missing its secret or API key.Set the missing environment variable.

Recorded examples#

These bodies were recorded from the running server.

400 validation_error

{
  "name": "ValidationError",
  "code": "validation_error",
  "message": "Request validation failed",
  "errors": [
    {
      "path": [
        "username"
      ],
      "message": "Expected string, received number"
    }
  ]
}

400 validation_error

{
  "name": "ValidationError",
  "code": "validation_error",
  "message": "At least one recipient is required",
  "errors": [
    {
      "path": [
        "to"
      ],
      "message": "at least one of to, cc or bcc is required"
    }
  ]
}

401 unauthorized

{
  "name": "AuthenticationError",
  "code": "unauthorized",
  "message": "Missing or invalid API key",
  "fix": "Send 'Authorization: Bearer <api_key>'"
}

403 missing_permission

{
  "name": "ForbiddenError",
  "code": "missing_permission",
  "message": "API key lacks permission: webhook_create"
}

403 email_unverified

{
  "name": "ForbiddenError",
  "code": "email_unverified",
  "message": "Verify your email address before sending mail or creating API keys",
  "fix": "Click the link in the verification email, or POST /auth/resend-verification"
}

404 not_found

{
  "name": "NotFoundError",
  "code": "not_found",
  "message": "Inbox not found"
}

402 plan_limit_exceeded

{
  "name": "PlanLimitError",
  "code": "plan_limit_exceeded",
  "message": "Free plan limit reached: inboxes (3/3)",
  "fix": "Upgrade to the Pro plan via POST /v0/billing/checkout (plan: \"pro\")",
  "errors": [
    {
      "limit": "inboxes",
      "current": 3,
      "max": 3,
      "plan": "free",
      "upgrade_to": "pro"
    }
  ]
}

422 recipient_suppressed

{
  "name": "UnprocessableError",
  "code": "recipient_suppressed",
  "message": "Recipient(s) suppressed after a bounce or complaint: gone@example.org",
  "fix": "Remove the address(es) with DELETE /v0/suppressions/{address} or send to a different recipient"
}

409 pod_not_empty

{
  "name": "ConflictError",
  "code": "pod_not_empty",
  "message": "Pod still has 1 inbox(es); delete them first",
  "fix": "Delete all inboxes in the pod"
}

400 invalid_webhook_url

{
  "name": "ValidationError",
  "code": "invalid_webhook_url",
  "message": "Webhook url must be a public https URL"
}

400 validation_error

{
  "name": "ValidationError",
  "code": "validation_error",
  "message": "range plus window divided by period must not exceed 1000 buckets"
}

422 reserved_username

{
  "name": "ValidationError",
  "code": "reserved_username",
  "message": "The username \"admin\" is reserved",
  "fix": "Choose a different username"
}

Troubleshooting#

401 unauthorized on every call. The header must be Authorization: Bearer am_... with the whole key. A key that was deleted, or whose scoped inbox was deleted, answers 401. A key in the ?api_key= query is accepted on /v0 routes for the benefit of browsers opening WebSockets, but URLs end up in proxy logs, so use the header everywhere else. The hosted MCP endpoint accepts the header only.

403 email_unverified when sending or creating a key. The owner email is not verified. Open the link in the verification email. Receiving works before that.

400 bad_request "Body cannot be empty when content-type is set to 'application/json'". You sent Content-Type: application/json on a request with no body, usually a DELETE or a body-less POST. Drop the header, or send {} on a POST. The SDKs do this for you.

404 not_found for something that exists. The resource is outside your key's scope (the API does not reveal that), or the inbox id in the path is wrong. Check that you are using a key for the right pod or inbox.

403 missing_permission. The message names the permission. Create a key that includes it, or an organization-level key without a list. Scoped keys also cannot create pods or inboxes.

402 plan_limit_exceeded. Read errors[0]: it names the limit and the plan that allows more. Delete unused inboxes or webhooks, or upgrade.

403 recipient_blocked. A block list matched, or a send allow list exists and the recipient is not on it. Lists are checked at inbox, pod, then organization scope; the first scope with a matching entry decides.

422 recipient_suppressed. The address bounced or complained before. Remove it with DELETE /v0/suppressions/:address only if you are sure it is valid.

A message I expected is missing from the list. Spam, blocked, unauthenticated and trash messages are hidden unless you pass that label. Filters such as labels=received,unread need every label to match.

My reply landed in a new thread. Forwards start a new thread. For replies use the reply endpoint, which sets In-Reply-To and References.

My webhook is not called. Check enabled, the event types it subscribes to, its inbox and pod scope, and GET /v0/webhooks/:webhook_id/deliveries for the last attempts. The URL must be a public https address. Respond with a 2xx within 10 seconds; anything else is retried up to eight attempts.

My webhook signature check fails. Verify against the exact raw request body, not re-serialised JSON. Strip whsec_ and base64-decode the rest to get the key. See webhooks.

I verified my domain but mail does not flow. Expected: sending from and receiving on custom domains is not connected yet. See domains.

429 rate_limited but I am under my plan. The per-inbox cap of 100 sends in any rolling 24 hours applies on every plan, as do the per-IP and per-key request limits. Honour Retry-After.

A 5xx or a connection error. Retry with backoff and the same Idempotency-Key. The service runs on one server in early access, so short interruptions during deploys happen. See status.

The dashboard shows an error banner but the API works. Dashboard requests use a session cookie and a CSRF token; 403 csrf_failed means the page was open too long or came from another origin. Reload it.