Errors
Every failure has the same shape:
{ "error": { "code": "SCOPE_INSUFFICIENT", "message": "Cette requête demande la portée « ws:write », que cette clé n'a pas. Elle porte : ws:read." }}Never a bare { "message": … }, on any route — an invariant in this repository
holds that shape.
Some refusals carry extra fields alongside the two above — a plan refusal names the plan you are on and the one that lifts it. They are documented on the operations that produce them in the reference.
Authentication and authorisation
Section titled “Authentication and authorisation”| Status | Code | What happened |
|---|---|---|
| 400 | AMBIGUOUS_CREDENTIALS |
An API key and a session cookie in one request. Refused rather than settled arbitrarily. |
| 401 | UNAUTHENTICATED |
No credential at all. |
| 401 | KEY_UNKNOWN |
This key never existed. Check what your CI is sending. |
| 401 | KEY_REVOKED |
Somebody took it away. Mint a new one. |
| 401 | KEY_EXPIRED |
The clock did its job. Rotate. |
| 403 | SCOPE_INSUFFICIENT |
The message names the scope this request wanted, and the ones your key holds. |
| 403 | KEY_CANNOT_REACH |
No scope opens this route to a key. Not an oversight — see Authentication. |
| 403 | KEY_CANNOT_INVITE |
A key lets nobody into the organization, whatever its scope. |
| 403 | KEY_CANNOT_DELETE_WORKSPACE |
Delete from the application. |
| 403 | FORBIDDEN |
The caller may not manage this workspace. |
Not found
Section titled “Not found”404 answers both “this does not exist” and “this exists and is not yours”,
identically and on purpose: INBOX_NOT_FOUND, MESSAGE_NOT_FOUND,
WORKSPACE_NOT_FOUND.
Telling the two apart would let anybody map a colleague’s workspaces one request at a time.
A search over an inbox that is not yours therefore answers 404, not an empty
list. An empty list reads as “the mail has not arrived” and sends somebody
debugging a pipeline that works.
Search criteria
Section titled “Search criteria”| Code | What happened |
|---|---|
CRITERIA_EMPTY |
No criterion given. An empty search is not a search — for “the last messages”, read GET /v1/inboxes/{id}/messages. |
CRITERIA_UNKNOWN |
A field that is not a criterion. The message names it. |
CRITERIA_INVALID |
A criterion whose value is unusable — the message names the field. |
CURSOR_INVALID |
Not a cursor we issued. Take the one from the previous response, or omit it. |
INVALID_JSON |
The body did not parse. |
CRITERIA_UNKNOWN is worth a sentence, because it is a refusal you might read as
strictness and it is the opposite. Write subjet for subject and the request
fails, naming the field. Ignored instead, that typo would have matched every
message in the inbox, your test would have asserted on the first one, and it
would have passed until the day a second message arrived. The 400 costs a
minute; the silence costs an afternoon.
Plan ceilings
Section titled “Plan ceilings”402, always with currentPlan and requiredPlan beside the code, so a script
can report what to change:
| Code | What happened |
|---|---|
INBOX_LIMIT_REACHED |
The plan includes fewer inboxes. Counted for the organization, not per workspace. |
WORKSPACE_LIMIT_REACHED |
Same, for workspaces. |
INBOX_QUOTAS_UNAVAILABLE |
Raising a per-inbox ceiling needs a higher plan. Removing one stays open on every plan — a customer who cannot undo their own configuration has to write to us. |
Throttling
Section titled “Throttling”| Status | Code | What happened |
|---|---|---|
| 429 | TOO_MANY_WAITS |
Your organization holds 20 open waits already. Usually a suite firing searches without awaiting their answers: lower the concurrency, or lower wait. |
In a message
Section titled “In a message”A message can carry an error of its own, in place of its parsed fields, when it was accepted but not processed:
{ "id": "…", "status": "quota_exceeded", "error": { "code": "PLAN_QUOTA_EXCEEDED", "message": "…" }}The derived keys — subject, otp, links, extract — are absent rather
than null. An absent key pushes a client to read status; a plausible-looking
null is something a test asserts against and passes for the wrong reason.