Errors
Every refusal has the same shape, application/problem+json, on every route:
{ "type": "/errors/UNAUTHENTICATED", "title": "Unauthorized", "status": 401, "detail": "Authentication is required to access this resource.", "code": "UNAUTHENTICATED"}You can see it by calling the API without a key:
curl --silent --show-error --include https://api.facteur.eu/inboxesBranch on code
Section titled “Branch on code”code is the member your client tests against. detail is a sentence in English for the person who reads a red pipeline, and its wording can change. Do not parse it and do not assert on it. To show a refusal in another language, translate the code.
A refusal that needs a value to be useful carries it as a member of its own, next to detail. The 403 of a missing permission has permission, and the operation that can answer it documents it in the reference. A validation failure (VALIDATION_FAILED) lists each field in violations, with its propertyPath, its message and its own code.
Authentication
Section titled “Authentication”| Status | Code | What you do |
|---|---|---|
| 401 | UNAUTHENTICATED |
Add the Authorization header. |
| 401 | API_KEY_UNKNOWN |
Check what your pipeline sends. This key never existed. |
| 401 | API_KEY_REVOKED |
Create a new key. A person revoked this one. |
| 401 | API_KEY_EXPIRED |
Create a new key, and do it before the date next time. |
| 403 | API_KEY_PERMISSION_MISSING |
Give the key the permission named in permission, or call an operation it already reaches. |
| 403 | API_KEY_CANNOT_DELETE_WORKSPACE |
Delete the workspace from the Facteur app. No key does it. |
Authentication explains the permissions and the three reasons for a 401.
Not found
Section titled “Not found”| Code | What you do |
|---|---|
INBOX_NOT_FOUND |
Check the identifier, then check that the key reaches the workspace of the inbox. |
MESSAGE_NOT_FOUND |
The same, for a message. |
WORKSPACE_NOT_FOUND |
The same, for a workspace address. |
MEMBER_NOT_FOUND |
The person is not a member of this workspace. |
EXTRACTOR_NOT_FOUND |
Check the identifier of the extractor and the inbox it belongs to. |
ATTACHMENT_NOT_FOUND |
Check the identifier of the attachment. |
ATTACHMENT_NOT_STORED |
Facteur never kept the bytes of this attachment. Its reason in the message says why. |
SMS_REQUEST_NOT_FOUND |
This inbox has never asked for an SMS number. Ask for one with a POST on the same path. |
All of them answer 404. A resource that belongs to another organization, or to a workspace the key does not reach, gets the same 404 as one that does not exist.
A search on an inbox the key does not reach answers 404 too, and never an empty list. Tell the two apart in your test: an empty list means the mail has not arrived, a 404 means you are looking in the wrong place.
curl --silent --show-error --include \ -H "Authorization: Bearer $FACTEUR_API_KEY" \ https://api.facteur.eu/inboxes/zzzzzzzz/messagesSearch
Section titled “Search”| Status | Code | What you do |
|---|---|---|
| 400 | CRITERIA_EMPTY |
Name a set: an inbox, a recipient, a subject, a code or a date. |
| 400 | CRITERIA_UNKNOWN |
Fix the field the detail names. It is not a criterion of the search. |
| 400 | CRITERIA_INVALID |
Fix the value of the field the detail names: a text over its longest length (Limits lists them), a limit that is not a whole number, a date that is not one, a boolean that is not true or false. |
| 400 | INVALID_CURSOR |
Send back the nextCursor of the previous page, unchanged, or leave cursor out. |
The two forms of the search, GET/messages and POST/messages/search, answer the same codes. A POST whose body is not JSON answers 400 BAD_REQUEST.
CRITERIA_UNKNOWN is the one that surprises people. If subjet were ignored in place of subject, the search would match every message of the inbox, your test would assert on the first one, and it would pass until a second message arrived.
curl --silent --show-error --include --get \ -H "Authorization: Bearer $FACTEUR_API_KEY" \ --data-urlencode "inbox=j3k9x2mq" \ --data-urlencode "subjet=code" \ https://api.facteur.eu/messagesThe other refusal of a search is a value too long. A subject is at most 255 characters, and a longer one is refused instead of being cut, because a cut text would match other messages:
curl --silent --show-error --include --get \ -H "Authorization: Bearer $FACTEUR_API_KEY" \ --data-urlencode "subject=$(printf 'a%.0s' {1..300})" \ https://api.facteur.eu/messagesThrottling and availability
Section titled “Throttling and availability”| Status | Code | What you do |
|---|---|---|
| 429 | RATE_LIMITED |
Wait the number of seconds in Retry-After, then send fewer requests. A key may make 600 a minute. |
| 429 | TOO_MANY_WAITS |
Await each search before you send the next, lower your worker count, or shorten wait. |
| 503 | API_DRAINING |
Send the same request again after the Retry-After delay. The instance that received it was stopping. |
| 503 | ATTACHMENT_STORE_UNAVAILABLE |
Try the download again a little later. |
The two 429 count different things, so the code tells you what to change. Limits gives the numbers.
Plan ceilings
Section titled “Plan ceilings”These answer 402. They come from the plan of the organization, not from the key:
| Code | What you do |
|---|---|
INBOX_LIMIT_REACHED |
Delete an inbox, or change the plan. |
WORKSPACE_LIMIT_REACHED |
Delete a workspace, or change the plan. |
INBOX_QUOTAS_UNAVAILABLE |
The plan does not include per-inbox ceilings. Removing a ceiling works on every plan. |
WORKSPACE_QUOTAS_UNAVAILABLE |
The plan does not include per-workspace ceilings. Removing a ceiling works on every plan. |
SMS_NUMBER_LIMIT |
The plan holds no more SMS numbers. limit and occupied say how many it holds and how many are taken, requiredPlan names the plan that holds one more. Take back a request, or change the plan. |
SMS numbers
Section titled “SMS numbers”A request for an SMS number is read by a person of the Facteur team, who gives the number. These are the refusals of the request itself:
| Status | Code | What you do |
|---|---|---|
| 400 | SMS_COUNTRY_NOT_OPEN |
Ask for a country listed in countries, or leave country out for the United Kingdom. |
| 400 | SMS_NOTE_TOO_LONG |
Shorten note to max characters. |
| 409 | SMS_REQUEST_ALREADY_OPEN |
The inbox already has a request waiting. Read it with a GET, or take it back before you ask again. |
| 409 | SMS_REQUEST_NOT_WITHDRAWABLE |
The request was already declined or taken back: there is nothing left to take back. |
A message refused at reception
Section titled “A message refused at reception”Sometimes a message arrives while a limit is reached. Facteur stores it with the status quota_exceeded and reads nothing from it. It carries the reason in errorCode, and none of the members read from the body (subject, otp, links, extract). Read status before you read any of them.
errorCode |
What you do |
|---|---|
PLAN_QUOTA_EXCEEDED |
The capacity of the plan is spent for this period. |
INBOX_QUOTA_EXCEEDED |
Raise or remove the ceiling you set on this inbox. |
WORKSPACE_QUOTA_EXCEEDED |
Raise or remove the ceiling you set on this workspace. |
OVERAGE_CAP_REACHED |
Raise the spending cap you chose, or buy more capacity. |
DAILY_SPIKE_CAP |
Wait for the next day. |
SUBSCRIPTION_SUSPENDED |
Settle the outstanding invoice. |
These are a person’s decisions in the Facteur app. An API key cannot change any of them.
The routes of this API are listed in the reference, each with the refusals it can answer. For example, GET/messages can answer the codes of the search table above.