Skip to content

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-ID header, backd uses it: 1–128 characters from A–Z a–z 0–9 . _ : -.
  • Otherwise backd generates one.
  • The ID is always returned in the X-Request-ID response 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 with 413 payload_too_large.
  • POST and PUT require Content-Type: application/json.
  • PATCH requires Content-Type: application/merge-patch+json.
  • The only parameter allowed on these content types is charset=utf-8. Anything else returns 415 unsupported_media_type.

Caching

Responses depend on who is asking, so backd tells caches (browsers, proxies, CDNs) how to keep them apart:

ResponsesHeaders
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 routesAlso 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.publicCache-Control: public, max-age=<n> (at most 3600), so a CDN may store them
A document read with ?file_links=trueCache-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.

HTTPcodeWhen
400invalid_jsonBody is not a single valid JSON object
400validation_errorDocument fails its schema, or an /_auth or /_admin request body is invalid (details per path)
400invalid_headerMalformed If-Match header, or an X-Backd-On-Behalf-Of header that isn’t allowed or names no user of the realm
400id_requiredA create in a collection with custom_id without an id
400invalid_idA create’s id that is outside the safety envelope or the pattern, or isn’t the creating user’s with custom_id: user
400id_not_allowedA create with an id in a collection that generates its ids
400invalid_expandAn 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
400expand_too_largeAn expand that would return more documents than the instance allows; nothing partial is returned
400invalid_aggregateAn aggregate request whose group_by, metrics, order_by, limit, tz or where is wrong; the details name each one. See Aggregations
400aggregate_not_enabledAn aggregate request to a collection that declares no aggregate:
400search_not_enabledA $search in a collection that declares no text index
400invalid_queryBad where, order_by, limit, skip or count, or unknown query parameter
400idempotency_key_requiredA function with idempotency: required was called with no Idempotency-Key
401unauthenticatedMissing, unknown, expired or revoked session token or API key, where credentials are required (see API keys)
401invalid_credentialsWrong email or password
403forbiddenThe 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
404not_foundUnknown route, realm, database, collection or id, or a document the caller’s access rules hide
405method_not_allowedMethod not supported on the route
409conflictA unique index is violated (details name the fields)
409id_takenA create with a custom id that exists
409write_conflictA PUT, PATCH or conditional DELETE lost the race against concurrent writes 3 times
409email_takenSign-up with an email that is already registered
409request_in_progressA function call with this Idempotency-Key is still running
412version_mismatchIf-Match doesn’t match the current version
403invalid_file_linkA file link that was changed, has expired or whose key was rotated
404file_missingThe document holds a file the storage no longer has
404version_unavailable?version= names an image version that isn’t ready, with its status and reason in details
409too_many_filesA multiple file field is at max_files
409upload_mode_mismatchA proxy upload to a direct field, or a direct start on a proxy field
409file_not_uploadedA direct upload was completed before its file reached the bucket
413payload_too_largeBody exceeds MAX_BODY_BYTES; an upload, max_size or BACKD_MAX_UPLOAD_BYTES
422invalid_imageA JPEG or PNG too damaged to be copied without its metadata
413quota_exceededAn upload would pass the realm’s or the owner’s storage quota
415unsupported_file_typeThe type detected from an upload isn’t one the field accepts
416range_not_satisfiableA Range outside the file
415unsupported_media_typeWrong Content-Type for the method (a webhook function’s body isn’t required to be JSON at all)
422upload_mismatchA direct upload’s size or SHA-256 isn’t the declared one: the object was deleted
422idempotency_key_reusedA function Idempotency-Key was already used with different input
422aggregate_too_expensiveAn aggregation that needed more time, memory or distinct values than allowed: narrow where, group by fewer fields or add an index
422search_too_expensiveA $search that needed more time than allowed: fewer or rarer words, or a narrower where
503index_buildingA $search while the collection’s text index is being rebuilt after its declaration changed; retry after Retry-After
429too_many_requestsToo 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
500function_failedA function threw an error other than ctx.error, crashed, or went over its memory or CPU limit
500internal_errorUnexpected failure. Details are only in the logs
500invalid_outputA function’s output is larger than max_output, or doesn’t match output.schema.json
500secret_missingA function declares a secret that isn’t set for its database or realm
503storage_unavailableA realm’s file storage can’t be reached, or its keys aren’t set
503unavailableNot 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)
504function_timeoutA 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:

EndpointMeaning
GET /healthzThe process is alive: always 200 {"status":"ok"}
GET /readyzConfig 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)
Edit this page · Last updated 19 Oct 2026