---
title: "Get Receipt"
method: GET
path: "/api/v1/receipts/{receipt_id}"
tags: ["receipts"]
---

# Get Receipt

`GET /api/v1/receipts/{receipt_id}`

## Request

- Base URL: `https://statsmapped.com`
- URL: `https://statsmapped.com/api/v1/receipts/{receipt_id}`
- Auth: none declared

"Show me how you got that" -- E2's own acceptance bar. Returns the exact
recipe/series_versions/result this receipt was written with, PLUS `replay`: a
fresh re-run of the same recipe, hash-compared against the stored result --
OI's own pure, DB-free replay_receipt() (receipt_hash.py, #449) does the
comparison and, on a mismatch, explains which tracked series_versions entry
changed. `result` itself is always the ORIGINALLY STORED answer (never swapped
for the fresh re-run's own result, even on a mismatch) -- a receipt's job is to
say what was actually served.

REPLAY NEVER RECORDS A NEW RECEIPT as a side effect of checking one (R2C review
of #452, non-blocking item (c)) -- `_chat_check_comparability(..., record=
False)` is the non-recording variant built specifically for this call site.

REPLAY NEVER BLOCKS THE RAW READ (same "secondary concern never breaks the real
response" discipline `_record_receipt()` itself already follows) -- any failure
(an unknown/future `query_kind` this function doesn't know how to re-invoke, a
transient DB error during the fresh re-run) degrades `replay` to None rather
than 500ing a request whose whole point is "show me what was already recorded".

`facts_drift`/`method_drift` (#802, panel finding 5, migration 098): additive
to `replay` above, not a replacement. `replay['explanation']` can only ever
INFER "the method may have changed" from the ABSENCE of a data explanation;
`facts_drift` is a direct field-by-field diff of the actual facts dict
evidence_verdict.evaluate() decided from (stored at write time vs. freshly
recomputed here), and `method_drift` directly compares this receipt's own
pinned deploy_commit against the CURRENT one -- real, nameable signals rather
than an inference. Both None when the receipt predates migration 098 (never
captured `facts`/`method` at write time) -- that is NOT the same as "no
drift", so this never fabricates a clean bill of health for a receipt that
simply has no baseline to diff against.

## Path parameters

- `receipt_id` integer, required

## Response `200`

Successful Response

- Receipt: Campaign E2 (wl:85fe273dcd5b/wl:ae4ca5355d7c): GET /receipts/{receipt_id}'s own response -- migration 093's stored row, read back, plus a live replay. `replay` (wired in via OI's receipt_hash.py, #449 -- schema corrected here to match that module's REAL contract, not the shape this docstring used to describe before the two sides were reconciled, R2C review of #452 item (b)): None only when this receipt's own `query_kind` isn't a replayable type yet, or the fresh re-run itself failed (never blocks this route's raw read). Otherwise a dict with `reproduced` (bool -- a fresh hash match against the stored `result_hash`), `explanation` (None when reproduced; otherwise a list of {"identity", "changed_fields"} dicts naming which `series_versions` entries moved, or a single explanatory note when nothing tracked explains the drift -- the rule logic itself may have changed, not the data), and `fresh_result` (the fresh re-run's own answer, present only when `reproduced` is False -- `result` above is always what was actually served, never swapped for this). `facts`/`method` (migration 098, #802): the complete facts dict evidence_ verdict.evaluate() decided from, and which evaluator/deploy commit was live, both pinned at write time. None for a receipt written before migration 098. `facts_drift`/`method_drift` (api.py's own `get_receipt()`, additive to OI's `replay` above, not a replacement): a field-by-field diff between the stored facts and a FRESH recomputation, and whether the current deploy_commit differs from the receipt's own pinned one -- a more specific signal than inferring "the method may have changed" from series_versions alone (which `replay`'s own `explanation` still does, unchanged). Both None when `facts`/ `method` weren't captured at write time (a pre-098 receipt) or the replay itself couldn't run.
  - `country` string
  - `receipt_id` integer, required
  - `created_at` string, required
  - `query_kind` string, required
  - `recipe` object, required
  - `series_versions` object[], required
  - `result` object, required
  - `replay` object, nullable
  - `facts` object, nullable
  - `method` object, nullable
  - `facts_drift` object[], nullable
  - `method_drift` object, nullable
  - `vintage` object, nullable

## Other responses

- `400`: Bad request
- `401`: Missing or invalid credentials
- `403`: Not authorized for this request
- `404`: Not found
- `422`: Validation error
- `429`: Rate limited

---

[API](https://skmtc.dev/statsmapped/apis/statsmapped-api.md) · [All operations](https://skmtc.dev/statsmapped/apis/statsmapped-api/llms.txt) · [OpenAPI document](https://skmtc.dev/statsmapped/apis/statsmapped-api/revisions/7c5b264d05b7?raw)
