Receipt fields #
A receipt is JSON signed by your Tabslip. show --json returns an envelope that
holds the receipt and its signature, using the DSSE format. Decode the base64
payload to read the fields below. A download keeps the original signed bytes.
Replace ./instance-data with the data directory Tabslip prints:
tabslip receipts --data-dir ./instance-data --limit 0 --jsonUse a returned id with tabslip show <receipt-id> --data-dir ./instance-data
for a terminal receipt, or add --json for the envelope. Open /r/<receipt-id>
on the instance's Page URL for the paper view, signature results and details.
Signed base fields #
Nullable fields are JSON null when unknown. Zero means a reported or
measured zero, never a substitute for missing data. Hashes are SHA-256 hex.
| Field | Meaning |
|---|---|
type, id |
tabslip.receipt/v1 and locally generated rcpt_ id |
instance.key_id, instance.tabslip |
Signing key id and Tabslip version |
time.started, first_byte, finished |
Observed UTC timestamps; first byte can be null |
time.ttfb_ms, total_ms |
Observed milliseconds to first byte and total duration |
request.upstream, api, method, path |
Selected route, adapter, HTTP method and API path |
request.model, stream |
Requested model and streaming flag, nullable |
request.params |
Selected parameters, not messages or tool arguments |
request.tag |
Optional validated client tag; omitted when absent |
request.body_sha256, body_bytes |
Original client body hash and byte count before edits |
request.modifications |
Recorded edits, such as usage injection |
response.status, complete |
Observed HTTP status (nullable) and capture completeness |
response.api_status |
Responses API terminal status, nullable |
response.id, model, provider |
Provider-reported identifiers, nullable |
response.finish_reason, system_fingerprint |
Provider-reported fields, nullable |
response.error |
Null or {type, code}; no provider error message |
response.body_sha256, body_bytes |
Observed response bytes hash and size |
usage.source |
reported or unknown |
usage.input_tokens, output_tokens |
Reported input/output; embeddings have no output count |
usage.cached_input_tokens, cache_write_tokens |
Cache reads and total cache writes, nullable |
usage.cache_write_1h_tokens |
One-hour subset of cache writes, nullable |
usage.reasoning_tokens |
Reported reasoning subset of output, nullable |
cost.source, amount, currency |
reported, estimated or unknown; decimal string and currency, nullable |
cost.estimate, price_table |
Estimate retained alongside reported cost and attempted table version, nullable |
findings |
Array of {code, level, text} |
The request.params object keeps selected generation settings, such as
temperature and token limits. It can also keep routing options and the number
of tools. It excludes messages and tool arguments.
Tabslip hashes the body bytes it receives. For streams, it hashes the forwarded bytes rather than reconstructing answer text. Metadata parsing is capped at 10 MiB per JSON value or SSE event; forwarding and hashing continue when metadata cannot be decoded.
For Anthropic, signed input_tokens is uncached input. Displays add cache
reads and total cache writes. If any category is unknown, the displayed input
total is unknown. One-hour writes are already inside total writes. Signed
JSON and decoded JSONL keep the raw categories used for pricing.
response.complete describes capture, not successful model output: fully
received Responses incomplete and failed events can be complete captures.
JSON example #
This made-up OpenRouter example shows decoded JSON before supplemental cost arrives. It has no signature. A stored receipt has full ids and hashes where this example uses shortened values:
{
"type": "tabslip.receipt/v1",
"id": "rcpt_…",
"instance": {"key_id": "ts_…", "tabslip": "dev"},
"time": {"started": "2026-10-21T09:00:00.000Z", "first_byte": "2026-10-21T09:00:00.030Z", "finished": "2026-10-21T09:00:00.050Z", "ttfb_ms": 30, "total_ms": 50},
"request": {"upstream": "openrouter", "api": "openai.chat", "method": "POST", "path": "/v1/chat/completions", "model": "openai/gpt-4.1", "stream": false, "params": {}, "tag": "example", "body_sha256": "…", "body_bytes": 74, "modifications": []},
"response": {"status": 200, "complete": true, "api_status": null, "id": "gen-example-unpriced", "model": "openai/gpt-4.1", "provider": null, "finish_reason": "stop", "system_fingerprint": null, "error": null, "body_sha256": "…", "body_bytes": 300},
"usage": {"source": "reported", "input_tokens": 12, "output_tokens": 4, "cached_input_tokens": 0, "cache_write_tokens": null, "cache_write_1h_tokens": null, "reasoning_tokens": null},
"cost": {"source": "unknown", "amount": null, "currency": null, "estimate": null, "price_table": null},
"findings": [{"code": "price_unknown", "level": "observed", "text": "The cost is unknown."}]
}Findings #
| Code | Level | Meaning |
|---|---|---|
model_alias_resolved |
reported | A dated version of the requested model; informational, does not activate the findings filter |
model_differs |
reported | Requested and reported model differ |
fallback_used |
reported | Provider reports a later fallback model or multiple attempts |
usage_missing |
observed | No reported usage |
price_unknown |
observed | No reported cost or usable estimate |
stream_interrupted |
observed | Stream lacks its terminal event or client disconnected |
upstream_error |
observed | HTTP error or provider error event/object |
transport_error |
observed | Connection, TLS, read or early cancellation failure |
metadata_unavailable |
observed | Metadata could not be parsed within the limit |
Expected unknown cost on demo, Ollama and custom upstreams does not itself
produce price_unknown.
These describe reported values and observed failures; none checks the model itself.
Signed supplements and displayed totals #
OpenRouter can add a second DSSE envelope with payload type
application/vnd.tabslip.supplement+json. Its type is
tabslip.supplement/v1; receipt_id and receipt_sha256 bind it to the
base payload. source is openrouter.generation and fetched is its time.
Nullable provider, cost, data_region, and native_tokens.prompt and
completion come from generation details. currency accompanies cost.
attempts lists reported provider/status pairs; findings carries additional
findings. Missing lists are empty. Provider details can arrive after the call.
The signed base receipt never changes. The page and unsigned exports count supplement cost once. They keep the existing provider when the supplement's provider is null.
When a supplement supplies a missing cost, price_unknown is marked resolved.
The original finding stays signed and visible. The receipt page lists findings
from the base receipt first, then from supplements. Each finding names its
signed source. Findings from different sources stay separate even when their
codes match. Resolved findings stay out of the Calls badge and findings filter.
The receipt page and the terminal show command verify the original receipt
and every attached supplement before showing SIGNED. An invalid receipt or
supplement produces INVALID with the failed check; show exits 10.
show --json still returns only the original receipt envelope. If the receipt
or any attached supplement fails verification, it prints diagnostics on stderr
and exits 10, even when the base envelope alone is valid.
Stored data that cannot be decoded is a read error (exit 2). A missing receipt
uses exit 5.
See verification, costs and the
command reference.