HTTP API
Request IDs, request bodies, errors and health endpoints.
OpenAPI specification
The whole HTTP API is described in api/openapi.yaml (OpenAPI 3.1): every endpoint, status, response body and header. The server is tested against it on every change (see API contract), so it can be used to generate or check clients. Collections’ fields depend on your configuration, so documents appear there as open objects with exactly described id and _meta. For the exact API of your realm, with each collection’s schema, use its generated document.
Request IDs
Every request gets a request ID:
- If the client sends a valid
X-Request-IDheader,backduses it: 1–128 characters fromA–Z a–z 0–9 . _ : -. - Otherwise
backdgenerates one. - The ID is always returned in the
X-Request-IDresponse header, included in every log line for the request, and included in error bodies.
Request bodies
- Bodies larger than
MAX_BODY_BYTES(default 1 MiB) are rejected with413 payload_too_large. POSTandPUTrequireContent-Type: application/json.PATCHrequiresContent-Type: application/merge-patch+json.- The only parameter allowed on these content types is
charset=utf-8. Anything else returns415 unsupported_media_type.
Caching
Responses depend on who is asking, so backd tells caches (browsers, proxies, CDNs) how to keep them apart:
| Responses | Headers |
|---|---|
Everything under /v1/ | Vary: Origin, whether or not the request has an Origin header, so an answer for one web origin is never served to another |
Document routes (/v1/{realm}/{database}/{collection}…) | Also Vary: Authorization, so a response cached for an anonymous caller is never served to a signed-in one, or the other way around |
| Document routes | Also Vary: Cookie (realms with session cookies) and Cache-Control: private, no-cache: shared caches never store the response, and browsers revalidate it before reusing it |
/_auth, /_admin, and function calls (_func, _jobs) | Cache-Control: no-store: tokens, account data and function results are never cached |
Document reads without credentials, in a collection with cache.public | Cache-Control: public, max-age=<n> (at most 3600), so a CDN may store them |
A document read with ?file_links=true | Cache-Control: private, no-store: the links expire, so the response is never stored or revalidated |
Revalidation is cheap: document and list reads answer 304 Not Modified to a matching If-None-Match. See Caching and conditional GET and Revalidating a read. Errors on these routes carry the same headers.
Errors
Every error uses the same JSON envelope. Success responses are never wrapped.
{
"error": {
"code": "validation_error",
"message": "document failed schema validation",
"details": [
{ "path": "email", "reason": "must match format \"email\"" }
],
"request_id": "d2fk1a30h2ipb32i2hh0"
}
}
details is omitted when empty. code is stable and meant for programs to check; message is meant for humans.
| HTTP | code | When |
|---|---|---|
| 400 | invalid_json | Body is not a single valid JSON object |
| 400 | validation_error | Document fails its schema, or an /_auth or /_admin request body is invalid (details per path) |
| 400 | invalid_header | Malformed If-Match header, or an X-Backd-On-Behalf-Of header that isn’t allowed or names no user of the realm |
| 400 | id_required | A create in a collection with custom_id without an id |
| 400 | invalid_id | A create’s id that is outside the safety envelope or the pattern, or isn’t the creating user’s with custom_id: user |
| 400 | id_not_allowed | A create with an id in a collection that generates its ids |
| 400 | invalid_expand | An expand that names an unknown reference or field, goes too deep, has too many entries or is malformed; the details name the part. See Expanding references |
| 400 | expand_too_large | An expand that would return more documents than the instance allows; nothing partial is returned |
| 400 | invalid_aggregate | An aggregate request whose group_by, metrics, order_by, limit, tz or where is wrong; the details name each one. See Aggregations |
| 400 | aggregate_not_enabled | An aggregate request to a collection that declares no aggregate: |
| 400 | search_not_enabled | A $search in a collection that declares no text index |
| 400 | invalid_query | Bad where, order_by, limit, skip or count, or unknown query parameter |
| 400 | idempotency_key_required | A function with idempotency: required was called with no Idempotency-Key |
| 401 | unauthenticated | Missing, unknown, expired or revoked session token or API key, where credentials are required (see API keys) |
| 401 | invalid_credentials | Wrong email or password |
| 403 | forbidden | The operation isn’t allowed: sign-up in a realm where it isn’t open, no access rule allows it, a session on the admin API, or acting on behalf of a disabled user |
| 404 | not_found | Unknown route, realm, database, collection or id, or a document the caller’s access rules hide |
| 405 | method_not_allowed | Method not supported on the route |
| 409 | conflict | A unique index is violated (details name the fields) |
| 409 | id_taken | A create with a custom id that exists |
| 409 | write_conflict | A PUT, PATCH or conditional DELETE lost the race against concurrent writes 3 times |
| 409 | email_taken | Sign-up with an email that is already registered |
| 409 | request_in_progress | A function call with this Idempotency-Key is still running |
| 412 | version_mismatch | If-Match doesn’t match the current version |
| 403 | invalid_file_link | A file link that was changed, has expired or whose key was rotated |
| 404 | file_missing | The document holds a file the storage no longer has |
| 404 | version_unavailable | ?version= names an image version that isn’t ready, with its status and reason in details |
| 409 | too_many_files | A multiple file field is at max_files |
| 409 | upload_mode_mismatch | A proxy upload to a direct field, or a direct start on a proxy field |
| 409 | file_not_uploaded | A direct upload was completed before its file reached the bucket |
| 413 | payload_too_large | Body exceeds MAX_BODY_BYTES; an upload, max_size or BACKD_MAX_UPLOAD_BYTES |
| 422 | invalid_image | A JPEG or PNG too damaged to be copied without its metadata |
| 413 | quota_exceeded | An upload would pass the realm’s or the owner’s storage quota |
| 415 | unsupported_file_type | The type detected from an upload isn’t one the field accepts |
| 416 | range_not_satisfiable | A Range outside the file |
| 415 | unsupported_media_type | Wrong Content-Type for the method (a webhook function’s body isn’t required to be JSON at all) |
| 422 | upload_mismatch | A direct upload’s size or SHA-256 isn’t the declared one: the object was deleted |
| 422 | idempotency_key_reused | A function Idempotency-Key was already used with different input |
| 422 | aggregate_too_expensive | An aggregation that needed more time, memory or distinct values than allowed: narrow where, group by fewer fields or add an index |
| 422 | search_too_expensive | A $search that needed more time than allowed: fewer or rarer words, or a narrower where |
| 503 | index_building | A $search while the collection’s text index is being rebuilt after its declaration changed; retry after Retry-After |
| 429 | too_many_requests | Too many failed logins (see brute-force protection); or a function, or its realm, is at its concurrency limit; or the caller is over a function’s rate limit. Wait Retry-After seconds |
| 500 | function_failed | A function threw an error other than ctx.error, crashed, or went over its memory or CPU limit |
| 500 | internal_error | Unexpected failure. Details are only in the logs |
| 500 | invalid_output | A function’s output is larger than max_output, or doesn’t match output.schema.json |
| 500 | secret_missing | A function declares a secret that isn’t set for its database or realm |
| 503 | storage_unavailable | A realm’s file storage can’t be reached, or its keys aren’t set |
| 503 | unavailable | Not ready, MongoDB unreachable, MONGO_OP_TIMEOUT exceeded, too many password operations at once, or the functions executor isn’t configured, doesn’t answer, or is at capacity (all with Retry-After where the caller can usefully retry) |
| 504 | function_timeout | A function didn’t finish within its timeout; it was stopped |
A function’s own errors, thrown with ctx.error(status, code, message), carry whatever 4xx status and lower-case code it chose — see Calling a function.
Health endpoints
These live outside the /v1 prefix:
| Endpoint | Meaning |
|---|---|
GET /healthz | The process is alive: always 200 {"status":"ok"} |
GET /readyz | Config loaded, provisioning done and MongoDB reachable: 200 {"status":"ready","config":"<fingerprint>"}, otherwise 503 unavailable. config is the fingerprint of the config the instance runs (see Deploying config) |
Create, read, replace, patch and delete documents; versions, If-Match and If-None-Match.
QueryingFilter with where, sort with order_by, paginate and count.
AggregationsSummarize documents for dashboards and counters with GET _aggregate: group by declared fields and date buckets in a time zone, count, sum, average, minimum, maximum and distinct counts, with the read rules applied first.
Full-text searchA guide to searching with a text index: declaring fields and weights, languages and stemming, the search syntax, relevance and paging, $search versus $contains, limits and what it doesn’t do.
Caching and conditional GETETags, If-None-Match and 304, what the server still does, public caching of anonymous reads, and when to poll.
RealtimeSubscribe to a collection’s changes over one server-sent event stream: the request, the events, resuming, limits and how to start from a known state.
Your realm's OpenAPIbackd openapi and /_openapi.json: an OpenAPI 3.1 document for one realm’s client API, generated from its config, for typed clients in other languages.
References and expansionDeclare references between collections, return the referenced documents with expand, and understand what it costs: syntax, response shape, null semantics, limits, startup errors, caching and the alternatives to joins.