Skip to content
machinemail

Errors

The error envelope, every error type, and the messages the API returns.

The envelope

Every /v0 error, and errors from /health, /inbound/* and /auth/*, has this body:

Error body
{
  "error": {
    "type": "validation",
    "message": "username may contain a-z, 0-9 and . _ + - (1-64 characters)",
    "param": "username"
  }
}
FieldTypeMeaning
error.typestringOne of the types below. The HTTP status always matches the type.
error.messagestringA human-readable sentence. Do not parse it; it can change.
error.paramstring, optionalThe request field or parameter at fault, when there is one.

There is no list of field errors: a validation error reports the first problem found.

Types

typeStatusMeaning
validation400The request is malformed or a field is invalid.
unauthorized401No credential, or the credential is unknown, revoked or expired. Comes with a WWW-Authenticate header.
forbidden403The credential lacks a permission or scope, or a policy forbids the action (paused inbox, suppressed recipient, blocked recipient, inbox cap).
account_suspended403MachineMail suspended the organization (writes refused; reads, exports and inbound still work) or its sending (no outbound; everything else works). message says which and how to appeal. Do not retry.
not_found404The resource does not exist, or exists outside the credential’s scope. Unknown routes too.
conflict409A uniqueness or state conflict (address taken, domain registered, pod not empty).
rate_limited429Too many requests. See Rate limits.
internal500An unexpected server error. The message is always Internal server error.
not_implemented501The deployment has no email transport configured, so it cannot send.

not_implemented is not in the OpenAPI ErrorResponse enum, but the API returns it.

A 401 always carries:

http
WWW-Authenticate: Bearer resource_metadata="https://api.machinemail.dev/.well-known/oauth-protected-resource"

An unknown route returns 404 with Route not found: <METHOD> <path>.

Messages by area

These are the messages the API returns today. param is shown where it is set.

Requests

StatusMessageparam
400Request body must be a JSON object
400<name> must be a numberthe parameter
400<name> must be an ISO 8601 timestampthe parameter
400<name> must be a listthe parameter
400q is requiredq
400format must be mbox or jsonlformat
400message_ids must be a list

Authentication and scope

StatusMessage
401Missing or invalid API key (Not signed in on cookie-only /auth/* routes)
403API key lacks the <permission> permission
403Only organization-scoped credentials can create API keys
403A key with limited permissions cannot create an unrestricted key (param: permissions)
403Requested permissions exceed the current key (param: permissions)
403Only organization-scoped credentials can create pods
403Only organization-scoped credentials can update the organization
403Only organization-scoped credentials can remove suppressions
403Complaint suppressions can only be removed by MachineMail support; email support@machinemail.dev (param: email)
403Only organization-scoped credentials can manage agent registrations
403Organization scope required
403Inbox-scoped credentials cannot create inboxes
403Inbox-scoped credentials cannot delete their inbox
403Inbox-scoped credentials cannot add domains
404Pod not found: <pod_id>
404Inbox not found: <inbox_id>

Inboxes

StatusMessageparam
400username may contain a-z, 0-9 and . _ + - (1-64 characters)username
400ttl_seconds must be a positive numberttl_seconds
400status must be active or pausedstatus
403Inbox limit reached for <domain> (<cap> per organization). Add your own domain for more.domain
404Domain not found: <domain>domain
409Inbox with email <email> already existsusername

Sending

StatusMessage
400At least one recipient is required
400Invalid recipient: <address>
400Too many recipients: <n> across to, cc and bcc. A message can have at most <max> recipients; send separate messages instead. (param: to; <max> is 50 by default)
400Too many recipients: <n> across to, cc and bcc. Sandbox inboxes can have at most <max> recipients per message. Have your operator claim this inbox to lift sandbox limits (see /auth.md). (param: to; unclaimed sandbox inboxes, <max> is 5 by default)
400attachments[<i>] needs content (base64) or url
400attachments[<i>].url is larger than 25 MB, or another reason the URL could not be fetched
403Inbox <inbox_id> is paused
403Recipient <address> is suppressed (<reason>)
403Sending to <address> is blocked by a send list
429Sandbox inboxes can send <n> messages per 24 hours. Have your operator claim this inbox to lift sandbox limits (see /auth.md). (with Retry-After; <n> is 10 by default)
429Sandbox inboxes can email <n> different recipients per 24 hours; this inbox has used <used>, and this message adds <new> new. Have your operator claim this inbox to lift sandbox limits (see /auth.md). (with Retry-After; <n> is 10 by default)
501No email transport configured. Cannot send messages.

The OpenAPI description of inbox status says sends to a paused inbox return 403 with code inbox_paused. The API returns 403 with type forbidden and the message above; there is no inbox_paused code in the body.

URLs (webhooks and attachment URLs)

400 when a URL is not http(s), contains credentials, points at a private or loopback address, or does not resolve. Examples: url must be an http(s) URL, url must point at a public host.

Other resources

StatusMessageparam
400send_at must be an ISO timestampsend_at
400Unknown event type: <type>event_types
400domain must be a valid hostnamedomain
400catch_all_ttl_seconds must be positivecatch_all_ttl_seconds
400name cannot be emptyname
400direction must be send, receive or replydirection
400type must be allow or blocktype
400entry is requiredentry
400email is requiredemail
400period must be between 1 and 86400 secondsperiod
400start must be before end
400window must be a multiple of periodwindow
400Unknown permission: <name>permissions
400expires_at must be an ISO timestampexpires_at
404<Resource> not found or <Resource> not found: <id> for threads, messages, drafts, webhooks, deliveries, domains, events, API keys, list entries, suppressions, trust reports, attachments and raw messages
409<domain> is a shared domaindomain
409Domain <domain> is already registereddomain
409The default pod cannot be deleted
409Pod still has inboxes; delete them first
409<collection> conflicts with an existing record

Raw injection (POST /inbound/raw)

StatusMessage
400Message too large (over 40 MB)
400No recipient: set X-Recipient or a To header
404No inbox found for recipients: <addresses> (param: recipient)
403Inbox <inbox_id> is paused
403API key lacks the inbound_raw permission (full-access keys too)

Agent onboarding errors

/agent/* and /oauth2/* follow OAuth conventions instead of the envelope. Their bodies look like this and carry Cache-Control: no-store:

OAuth error body
{
  "error": "authorization_pending",
  "error_description": "The claim has not been completed yet",
  "message": "The claim has not been completed yet"
}
Endpointerror values (status 400 unless shown)
POST /agent/identityinvalid_request, anonymous_not_enabled, service_auth_not_enabled, identity_assertion_not_enabled, invalid_issuer, invalid_signature, invalid_audience, expired, invalid_client_id, missing_verified_email, replay_detected, rate_limited (429, with Retry-After), login_required (401, with max_age), interaction_required (401, with claim_token and claim)
POST /agent/identity/claiminvalid_request, invalid_claim_token (404, or 401 with WWW-Authenticate: Bearer error="invalid_token"), claimed_or_in_flight (409), claim_expired (410)
POST /oauth2/tokeninvalid_request, unsupported_grant_type, invalid_grant, invalid_client (401), invalid_target, authorization_pending, slow_down, expired_token, access_denied
POST /agent/event/notify{"err": "...", "description": "..."} with invalid_request, invalid_issuer, invalid_key or invalid_audience

Human claim lookups in the dashboard (GET and POST /auth/claim) use the standard envelope: 400 user_code must be 6 digits, 404 Claim code (unknown or expired) not found, 403 This agent can only be claimed by <masked email>, 429 Too many claim attempts....

MCP errors

MCP tools report API errors as a tool result with isError: true. The text content is <type>: <message> and structuredContent is the error envelope above. Transport errors (wrong method, server failure) are JSON-RPC errors with code -32000.

Handling errors

  • Retry 429 after the number of seconds in Retry-After, when present.
  • Retry 500 with backoff. Do not blindly retry sends: a send that timed out may have gone out. Use drafts with a client_id when you need a retry-safe send.
  • Do not retry 400, 403, 404 or 409 without changing the request.