Search the docs
Find a page, a section, or an endpoint.
Errors
One envelope, one code, and what each means.
A failure is always the same shape, with an HTTP status that means the same thing as the code inside it.
{
"error": {
"code": "validation",
"message": "Enter only the part before the @.",
"field": "local_part"
}
}message is written for a person and is safe to show one. It never carries a
stack, a DNS response code or a provider's wording — if you are surfacing API
errors in your own interface, this is the string to use.
The codes
| Code | Status | What it means |
|---|---|---|
validation | 400 | The request is malformed. field says where. |
unauthenticated | 401 | No key, or a key that has been deleted. |
forbidden | 403 | This key may not do that: its access or its reach. |
blocked | 403 | A rule on the address refused it. Remove the rule. |
not_permitted | 403 | The address's send_policy refused it. |
held | 403 | Pidgeon Firewall is holding the message for a person. It is readable once they release it. See the Firewall. |
not_found | 404 | No such resource — or one that is not yours. |
conflict | 409 | It already exists, or something else changed first. |
idempotency_in_progress | 409 | The first request with that Idempotency-Key is still running. Retry after Retry-After. |
idempotency_mismatch | 409 | That Idempotency-Key went with a different request. Use a new one. |
gone | 410 | It was here, and has been deleted. |
resync | 410 | Too far behind to catch up: page again from the top |
entitlement | 402 | Your plan does not include this. |
abuse_blocked | 403 | The account is paused pending review. |
exfiltration_blocked | 403 | It carries text a key read in another conversation. Send it as a reply there. |
taken_over | 403 | A person has this conversation, so your key cannot send in it or take it back. They hand it back. See hand-over. |
rate_limited | 429 | Too many requests. See the headers below. |
unavailable | 503 | Ours, and temporary. Retry. |
internal | 500 | Ours, and unexpected. |
test_dropped | 504 | Test mode only: Pidgeon-Test-Fail: drop. The request was done. |
forbidden and not_found deliberately return the same sentence. Confirming
that a resource exists but belongs to somebody else is itself a leak, so the
API does not.
gone is the one place we do distinguish existed from never existed, and
it is scoped so that the distinction tells you nothing you did not already
have: you get 410 only for something deleted from a resource you can
already read. Asked about somebody else's, it is a 404 like anything
else. It is answered for as long as the event log remembers the deletion;
after that the honest answer is 404 again, because we no longer know.
Rate limit headers
Every successful response carries them, so a client can pace itself rather than discover the limit by hitting it.
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1757760060Reset is unix seconds. A 429 also carries Retry-After, in seconds, and a
503 carries it when we know how long to wait.