API
Errors
The shape of an error, every code the public API can answer, what causes it and what to do about it.
On this page
Every failure has the same shape, the same one the rest of the service uses:
{
"error": {
"code": "validation_failed",
"message": "name must be 3 to 48 lowercase letters, numbers or dashes",
"field": "name"
}
}codeis one of the eight codes below. Branch oncode, never onmessage.messageis for a person to read. It can change.fieldis present when one part of the request is at fault, and names it:name,entries[2],expiresAt,month, and so on.
The codes#
| Code | HTTP | What it means | What to do |
|---|---|---|---|
validation_failed |
400 | The request is not valid: a bad field, a bad cursor or limit, a query parameter the route does not take, a missing required parameter, or a month with no usage. field names it. |
Fix the request. It is safe to retry with the same Idempotency-Key — a 400 does not use the key up. |
unauthenticated |
401 | No key, a malformed key, an unknown key, a revoked key or an expired key. All five answer the same thing, on purpose. | Check the key is the one you think it is and that it is still active (GET /api-keys). Revoked and expired keys are also recorded in the audit log. |
forbidden |
403 | The key is read_only and the request changes something, or a key tried to create an admin key. |
Use an admin key, or have a person create the admin key. The refusal is recorded in the audit log. |
not_found |
404 | No such database, user, key or operation in this key's organization. Another organization's ids answer this too, as does X-Fenvix-Workspace naming another organization, and a databaseId that is not yours. |
Do not treat it as "deleted" without checking: it also means "not yours". |
conflict |
409 | The name is taken; the database is already being erased; the 101st active key; an Idempotency-Key reused with a different request, or while the first one is still running; or a repeat of a request whose answer held a secret. |
Read the message. A reused key with a different body needs a new key; a secret-bearing answer is never replayed, so start a fresh request. |
rate_limited |
429 | Over a limit — per key, or per address for failed keys. | Wait the number of seconds in Retry-After, then retry. A 429 frees an Idempotency-Key. |
internal |
500 | The request failed inside the service. One known case: a key, or a database user, was created but the answer could not be completed, so it was revoked or dropped again at once. | Retry. Quote the X-Request-Id if it keeps happening. A 5xx frees an Idempotency-Key. |
unavailable |
503 | A downstream part did not answer in time: the engine refused or did not answer a network allowlist change (nothing was saved), or database users are not available on this deployment yet. Retry-After may be set. |
Retry after Retry-After. For an allowlist change, nothing changed, so a plain retry is safe. |
Which codes a route can answer#
Every answer of every endpoint, including its errors, is listed on its own page: Databases, Database users, Network allowlist, Usage, Organization and API keys. Those lists come from the API contract itself, so they are exact.
Finding one request again#
Every answer, errors included, carries X-Request-Id. You can send your own — 8 to 64 characters of A-Z a-z 0-9 . _ - — and it is kept. Log it, and quote it when you contact support.
curl -sS -D - -o /dev/null "$FENVIX_API/organization" \
-H "Authorization: Bearer $FENVIX_KEY" \
-H "X-Request-Id: checkout-deploy-8831"Retrying safely#
Any POST, PUT or DELETE can carry Idempotency-Key (1 to 255 visible characters). For 24 hours:
| You send | You get |
|---|---|
| the same key, the same request | the first answer again, with Idempotent-Replay: true |
| the same key, a different request | 409 |
| the same key while the first request is still running | 409 ("still running") |
the same key, after a 400, a 429 or a 5xx |
the key is free again — fix and retry |
Answers that hold a secret — a new API key, a new database user's password — are never replayed: a repeat answers 409 and makes nothing. Keys are per organization and shared with the console's routes.
Pages#
Lists take limit (1 to 100, default 20) and cursor, and answer { "items": [...], "nextCursor" }. Pass nextCursor back as cursor for the next page; it is null on the last page. Cursors are opaque: do not build one or read anything out of it.
Unknown query parameters are refused#
A query parameter a route does not take is a 400 whose field names the parameter. It is not ignored, so a typo fails loudly instead of quietly doing the wrong thing.
Long operations are not errors#
Creating or erasing a database waits up to 20 seconds. If it is not finished by then the answer is 202 with the operation, a Location header and Retry-After — not an error. Poll GET /operations/{id} until status is succeeded or failed; a failed operation carries the same { code, message, field? } in its error.