Error codes
Every error from the eMailPlane API has the same shape. The code never changes meaning, so your app or agent can branch on it.
{ "error": {
"code": "scope.forbidden",
"message": "token lacks the scope required by this endpoint",
"retryable": false,
"human_action": true,
"remedy": "Ask the app owner for a credential with one of the required scopes.",
"next": "GET /v1/tokens/self",
"docs_url": "https://emailplane.com/developers/errors/#scope-forbidden",
"required_scope": "send",
"granted_scopes": ["read:inbox"]
} }
| Field | Meaning |
|---|---|
code | Stable and machine-readable. Branch on this, not on the message. |
message | One line for a person. It never repeats an email address or a token back to you. |
retryable | true when the same request can succeed later without you changing anything (a busy server, a spent hourly budget). |
human_action | true when a person has to do something first: approve, change a policy, hand a conversation back, or issue a new credential. |
remedy | What to do, in a sentence or two. When the server knows something more specific, this says it. |
next | A call that tells you more or gets you unstuck, when there is one. |
docs_url | This page, at the code. |
Some errors add details, such as field, reason, retry_after_seconds or required_scope. Your server lists every code with GET /v1/errors, and one with GET /v1/errors/{code}.
Credentials and permission
auth.missing
No credential was sent. Retry can work: no. A person must act: no.
Send Authorization: Bearer <token> with a token from your server. Next: GET /v1/tokens/self.
auth.invalid
The credential is not valid. Retry can work: no. A person must act: yes.
The token is unknown, malformed or revoked. Ask the app owner for a new one; retrying the same token will not help.
auth.rate_limited
Too many sign-in attempts. Retry can work: yes. A person must act: no.
Wait for the time in Retry-After, then try once. Retrying sooner extends the lockout.
scope.forbidden
This credential lacks the permission. Retry can work: no. A person must act: yes.
The body lists the scopes the endpoint accepts and the ones you hold. Ask the app owner for a credential with one of the required scopes. Next: GET /v1/tokens/self.
role.forbidden
Your role cannot do this. Retry can work: no. A person must act: yes.
No credential you can create fixes this; an operator or the owner has to do it.
tier.forbidden
Your plan does not include this. Retry can work: no. A person must act: yes.
The body names the capability and the plan that includes it. Mail you already have keeps working. Next: GET /v1/usage.
plan.limit_reached
Your plan’s allowance is used up. Retry can work: no. A person must act: yes.
The body shows the limit, what is used and what is included. Upgrade, add on, or free one up. Nothing that already exists is affected. Next: GET /v1/usage.
credential.inbox_bound
This credential is limited to one inbox. Retry can work: no. A person must act: no.
An inbox credential can read and send only as its own inbox, and only on inbox endpoints. Use an app-wide credential for anything else. Next: GET /v1/tokens/self.
The request
request.invalid
The request is not valid. Retry can work: no. A person must act: no.
Fix the field named in field (and reason, when present), then send again.
validation.template_unresolved_tokens
The template needs more values. Retry can work: no. A person must act: no.
Supply a value for every placeholder the body lists, then send again.
request.too_large
The request body is too large. Retry can work: no. A person must act: no.
Send a smaller body; limit_bytes is the ceiling for this endpoint.
idempotency.mismatch
That Idempotency-Key was used for a different request. Retry can work: no. A person must act: no.
Use a new key for a new request. A key always returns the first answer it was given.
conflict
It conflicts with what already exists. Retry can work: no. A person must act: no.
Read the current state, then decide whether your change is still needed.
not_found
Not found. Retry can work: no. A person must act: no.
Nothing with that id exists for this app. An id that belongs to another app reads the same way, on purpose.
Sending limits and reputation
quota.exceeded
Today’s sending quota is used up. Retry can work: yes. A person must act: no.
The quota resets at midnight UTC (retry_after_seconds). An operator can raise it. Next: GET /v1/tokens/self.
rate.capped
Sending is capped for now. Retry can work: yes. A person must act: no.
The cap follows this credential’s bounce and complaint record and lifts by itself as that improves. Next: GET /v1/reputation.
budget.exhausted
The sending budget is spent. Retry can work: no. A person must act: yes.
Complaints and bounces used up this credential’s sending budget. Clean the list; an operator can grant more. Next: GET /v1/reputation.
stream.quarantined
This stream is paused. Retry can work: yes. A person must act: no.
This one stream (transactional or bulk) is paused after complaints; your other stream still sends. Stop the mail that caused it and wait for the gradual reopen. Next: GET /v1/enforcement.
queue.backpressure
The server is catching up. Retry can work: yes. A person must act: no.
Nothing is wrong with your request. Wait for Retry-After, or send it as transactional mail.
send.unavailable
Sending is unavailable right now. Retry can work: yes. A person must act: no.
A safety check the send path never skips could not run, so nothing was sent. Try again shortly.
plane_mail.daily_cap
Today’s account-mail allowance is spent. Retry can work: yes. A person must act: no.
This credential sends the service’s own account mail (invites, receipts, reminders), which has its own daily limit and shares a daily ceiling with relayed mail. It resets by itself: wait for Retry-After. Next: GET /v1/plane-mail/status.
plane_mail.braked
Account mail is paused after complaints. Retry can work: no. A person must act: yes.
Too many recipients reported this account mail as spam, so it stopped. An operator has to review it and turn it back on; retrying will not help. Next: GET /v1/plane-mail/status.
Recipients
recipient.forbidden
This server may not deliver to that recipient yet. Retry can work: no. A person must act: yes.
Delivery is limited to an allowlist the operator controls until this server is cleared to send to anyone. No credential changes that.
recipient.suppressed
That recipient is suppressed. Retry can work: no. A person must act: no.
They complained, hard-bounced or unsubscribed. Remove them from your list; only an operator can lift it. Next: POST /v1/suppressions/check.
Inboxes and reading mail
inbox.not_active
That inbox no longer receives mail. Retry can work: no. A person must act: no.
The inbox was burned or has expired. Create a new one. Next: POST /v1/inboxes.
inbox.no_service_domain
This app has no domain for inboxes yet. Retry can work: no. A person must act: yes.
Inboxes are created at the app’s own domain. Set one up first.
address.taken
That address is already in use. Retry can work: no. A person must act: no.
Choose another local part, or leave it out to get a random one.
address.burned
That address was burned. Retry can work: no. A person must act: no.
A burned address is never issued again, so an old user’s mail cannot reach a new one. Choose another.
address.reserved
That address is reserved. Retry can work: no. A person must act: no.
Names such as postmaster and abuse belong to the mail system. Choose another.
view.agent_content_only
The agent view returns text only. Retry can work: no. A person must act: no.
Agents read the cleaned, wrapped text. HTML and raw bytes are not available in the agent view.
withheld.human_only
Only a person can open this message. Retry can work: no. A person must act: yes.
The message was withheld from agents (a lookalike sender, hidden content or a failed DMARC check). A signed-in person can review it.
wait.limit_reached
Too many waits are open. Retry can work: yes. A person must act: no.
Cancel or let some waits finish before arming another. Next: GET /v1/waits.
Agents: policy, drafts and conversations
agent.paused
Sending is paused for this agent. Retry can work: no. A person must act: yes.
Someone pulled the kill switch for this credential, its inbox or the app. A person or an admin resumes it. Next: GET /v1/send-policy.
agent.self_resume
A credential cannot lift its own pause. Retry can work: no. A person must act: yes.
A signed-in person, or another admin credential, resumes it.
loop.auto_submitted
That message was sent by a machine. Retry can work: no. A person must act: no.
Mail from auto-responders, mailing lists and bounce systems is never answered automatically, so two machines cannot reply to each other forever.
loop.circuit_open
Too many sends into one conversation. Retry can work: yes. A person must act: no.
The per-conversation limit stops a reply loop. It reopens after the window in your send policy. Next: GET /v1/send-policy.
budget.tool_calls
This hour’s call budget is spent. Retry can work: yes. A person must act: no.
Wait for the next hour (Retry-After), or ask a person to raise tool_calls_per_hour in the send policy. Next: GET /v1/send-policy.
draft.not_pending
That draft was already decided. Retry can work: no. A person must act: no.
A draft is approved, rejected or expires once. Read it to see what happened.
approval.human_only
Only a person can approve or reject. Retry can work: no. A person must act: yes.
Drafts are decided in the dashboard, or by replying APPROVE or REJECT from the approver’s mailbox. No credential can decide one.
thread.human_owned
A person has taken this conversation. Retry can work: no. A person must act: yes.
Do not answer. You can offer a reply with a draft; only a person hands the conversation back. Next: POST /v1/drafts.
thread.handback_human_only
Only a person can hand a conversation back. Retry can work: no. A person must act: yes.
Handing a conversation back to an agent is a person’s (or an admin’s) decision.
The server
internal
Something went wrong on the server. Retry can work: yes. A person must act: no.
Try again. If it keeps happening, the server log has the details; error bodies never do.
From the MCP server
These come from the MCP server itself, when the request never reached the API. They carry the same fields.
infra.control_plane_unreachable
The MCP server could not reach the API. Retry can work: yes. A person must act: no.
The eMailPlane API runs on your own server; check that it is running and that the MCP server is configured with its address, then try again.
infra.unparseable_response
The API answered with something that is not JSON. Retry can work: yes. A person must act: no.
Something between the MCP server and the API answered with a page that is not JSON. Try again; if it persists, check the proxy in front of the API.
custody.mailbox_handoff_unavailable
A mail-app password cannot be issued over MCP yet. Retry can work: no. A person must act: yes.
A person’s mail-app password cannot be handed over safely through MCP yet. Nothing was changed; ask the owner to issue it from the dashboard.
handoff.activation_unconfirmed
The new credential is saved but not yet confirmed active. Retry can work: yes. A person must act: no.
The new credential is saved in its local file. Call the same tool again with the same idempotency key to confirm it is active.