Docs
Get started

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:

JSON
{
  "error": {
    "code": "validation_failed",
    "message": "name must be 3 to 48 lowercase letters, numbers or dashes",
    "field": "name"
  }
}
  • code is one of the eight codes below. Branch on code, never on message.
  • message is for a person to read. It can change.
  • field is 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.

Shell
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.