Skip to content

API snapshots

An api target snapshots an HTTP endpoint's response — REST or GraphQL — as structured data: the status code, an allow-list of response headers, and the body. JSON bodies are parsed before snapshotting, so a change shows up as a semantic diff (~ $.body.total: 42 → 58), not a wall of re-serialized text, and numeric tolerance applies to number fields just like performance metrics.

Configure a target

json
{
  "kind": "api",
  "name": "checkout-total",
  "url": "http://localhost:3000/api/cart/42",
  "headers": { "authorization": "Bearer test-token" }
}

Options:

  • url (required) — the endpoint to call.
  • method — HTTP method. Defaults to GET, or POST when a body or query is set.
  • headers — request headers, sent as-is.
  • body — a raw request body, sent verbatim (set your own content-type header).
  • query / variables — GraphQL sugar: posts the standard { "query": …, "variables": … } JSON envelope with content-type: application/json. Use query or body, not both.
  • includeHeaders — response headers to keep in the snapshot (default: ["content-type"]). Headers are volatile by default — dates, request ids, rate-limit counters — so only this allow-list is stored.
  • timeoutMs — request timeout (default: the lifecycle wait timeout).

A GraphQL target:

json
{
  "kind": "api",
  "name": "orders-query",
  "url": "http://localhost:3000/graphql",
  "query": "{ orders(last: 3) { id total } }"
}

Snapshot model

json
{
  "kind": "api",
  "status": 200,
  "headers": { "content-type": "application/json; charset=utf-8" },
  "bodyType": "json",
  "body": { "total": 42, "updatedAt": "<iso-timestamp>" }
}
  • A response that declares JSON (content-type contains json) is parsed and snapshotted structurally; anything else — and JSON that fails to parse, which is itself a regression worth seeing — is stored as text ("bodyType": "text").
  • Mask rules run over every string, in headers and body alike, so dynamic values (ids, timestamps) stay stable across runs.
  • The status code is part of the snapshot, not an error: an endpoint whose contract is 404 can be baselined, and a drift to 200 fails the run like any other diff.

Comparing

dungbeetle test reports a status change first, then structural changes with their JSON paths:

~ status: 200 → 500
~ $.body.total: 42 → 58

Number fields respect the config's comparison.numericTolerance, so noisy metrics can drift within bounds without failing the run.

Source-available: CLI under FSL-1.1-ALv2, cloud server under BUSL-1.1. See Licensing.