Skip to content

Runtime

Decision audit logs

A receipt for every answer your production agent was served: the route, a digest of the query, the corpus version that answered, and a sha256 of the response bytes. Months later, what the agent knew has an answer you can recompute.

LIVE Send X-PIT-Audit: on with a Desk key. Nothing to provision before a run and nothing to turn off after it, because receipting is per request.

  • REST
  • X-PIT-Audit: on
  • Desk
  • json · jsonl

The artifact

One audited call, and the receipt it wrote

An audited request against a local server on 2026-08-27, hashed on the client with shasum, then the receipt the server wrote for that same request. The client computes one hash over the bytes it received and the server computed the other over the bytes it sent.

terminalthe client's own hash

$ curl -s -H "Authorization: Bearer pit_live_..." -H 'X-PIT-Audit: on' \
    'https://api.pit.aqx.llc/v1/news?ticker=SIVB&as_of=2023-03-10T23:59:59Z&limit=2' \
    | shasum -a 256
c45adddabc2821c015847211159cdbc6d7bd3ea63fb2723e20b4aa39ad9a35cf  -

GET /v1/audit-logone receipt

{
  "ts": "2026-08-27T18:58:31.817Z",
  "key_id": "key_84887c96e987ca3712993c8a073a7a6a",
  "route": "GET /v1/news",
  "params_digest": "701c3eae4f1dbcfaf5147f12dc07d0526f40680265498eb1ef170d0770317851",
  "corpus_version": "sha256:29336a245255fdc7be9b5b0b1a6e90053e958a78a58166dbefeaeedce05eea7a",
  "envelope_sha256": "c45adddabc2821c015847211159cdbc6d7bd3ea63fb2723e20b4aa39ad9a35cf",
  "request_id": "014f795d-67fb-4c97-a9dd-d8c1a97eb4c6"
}

envelope_sha256 is the hash above. request_id is the X-Request-Id that came back on the response, so a receipt and a support question point at the same call. params_digest is reproducible from the query string alone, and the recipe is on the reference page.

The failure it prevents

Reconstructing what the agent knew, months later

A customer, a regulator or your own postmortem asks what the agent was looking at when it acted. Application logs record what your code did, and they rarely record which version of the record answered it in a form somebody outside your team can check.

A hash over the exact bytes

envelope_sha256 covers the response body as you received it, trailing newline included, so anyone holding that response can recompute it in one command.

The corpus that answered

corpus_version is the content hash of the record at the moment of the answer, so a later backfill cannot quietly rewrite what the agent was shown.

Refusals are receipted

A 409 for a coverage hole is something the agent was shown, so it gets a receipt with its own digest and its own request id, the same as an answer.

How it works

Hashed on the way out, written after you have your answer

Receipting is switched on per request

X-PIT-Audit: on turns it on for one call. 1, true and yes work too, and the header name is case-insensitive. A backtest that wants receipts sends the header, and the same key's interactive queries stay out of the table. Nothing is provisioned before a run and nothing has to be switched off after it.

A request without the header costs one path-prefix test and one header lookup, benchmarked at 19 ns with no allocations on an Apple M4 Pro. Writing a receipt costs about 2.2 µs, and none of that is a database round trip: the write happens after the response has gone.

The row holds hashes, never a body

FieldWhat it records
tsWhen the response finished, RFC3339 UTC to the millisecond.
key_idThe key that asked, which is the id GET /v1/keys lists.
routeMethod and path. The query string is not in it.
params_digestsha256 of the canonical query string, 64 hex, always present.
corpus_versionThe X-Corpus-Version of that response.
envelope_sha256sha256 of the response body bytes.
request_idThe X-Request-Id, tying a receipt to a server log line.

The table has no body column under any name, and a test fails if one appears. Storing the values would mean keeping a copy of every answer your agent ever read, where storing the digest lets you show that the copy you kept yourself is the right one.

Loss is counted and published

Receipts are written after the response has gone, on a drain goroutine behind a bounded queue 1200 deep, which is one full minute of a Desk key's rate limit. A full queue drops rather than making the response wait, so a database stall shorter than a minute costs nothing.

Every audited response carries X-PIT-Audit-Dropped, the running process drop count. A caller who watches that number stay still knows the log has no hole. GET /v1/audit-log/stats publishes the whole tally, including lost, which adds dropped to failed: a write that returned an error counts as loss rather than as a success.

Integration

One header out, one route back

terminaloldest first · cursor-paged

$ curl -s -H "Authorization: Bearer pit_live_..." \
    'https://api.pit.aqx.llc/v1/audit-log?day=2026-08-27&limit=100'

$ curl -s -H "Authorization: Bearer pit_live_..." \
    'https://api.pit.aqx.llc/v1/audit-log?format=jsonl&day=2026-08-27&limit=1000' \
    > receipts-2026-08-27.jsonl

Receipts are owner-scoped and ordered by the instant they were served. The log is append-only, so a page you already read never changes and resuming a cursor gives you only newer entries. format=jsonl serves application/x-ndjson, one receipt per line, with the next token in X-PIT-Audit-Next-Cursor.

What is not receipted, and the gaps we know about

CaseWhy
No X-PIT-Audit headerOpt-in. The request never enters the path.
Paths outside /v1//health, the site, POST /t.
A key not on DeskCounted under plan_required.
A browser session, or a key we rejectedA receipt records a key id, and there is none. Counted under unattributed.
Replies that are not JSONThe OpenAPI document, an SSE stream, a 304. Counted under skipped.
Reading the logReading the receipts would write receipts, which would need reading.
  • MCP tool calls are not receipted. The MCP endpoint forwards Authorization and X-Forwarded-For onto the request it builds, and not the audit header.
  • Retention is 400 days, an annual audit cycle plus a quarter. There is no retention job yet, so rows are kept until one is written.
  • The two routes are not in the OpenAPI document yet.

Related

What sits either side of it

Upstream

Point-in-Time API

The answers being receipted, and the clock each one names.

Alongside

MCP Server

The same corpus for agents that speak MCP, on the REST path underneath.

Downstream

Contamination Certificates

A signed verdict on one evaluation run, for when a hash of your own is not enough.