Chio/Docs
LOGIN · JOIN

BuildAudit

Query & Audit Receipts

Query the signed receipt log by actor, action, outcome, guard, cost, and time range.

Machine-readable schemas live elsewhere

This page provides operational queries. For parameter lists, response shapes, and error codes, see the Receipt Query API reference. That reference defines the API contract.

Prerequisites

  • A receipt store, and one of two ways in. Locally, a sqlite file named by --receipt-db. Remotely, a chio trust serve pointed at one, reached through --control-url and --control-token.
  • A read boundary, on the local path only. --tenant <id> or --admin-all. A local read with neither is refused. A remote read with either is also refused, because the token already carries the scope.
  • An admin token for the analytics endpoint. /v1/receipts/analytics requires admin read authority; a tenant-scoped token gets 403.
  • jq. Every filter this page cannot push to the server is a jq expression over JSON Lines.

The refusal on a local read with no boundary is the first thing most readers hit:

receipt-audit · list-no-boundarytranscript
$ chio --receipt-db ./receipts.db receipt list
error [urn:chio:error:cli:other]: --tenant <id> or --admin-all is required for local receipt reads
context: {"domain":"cli","severity":"error","stability":"deprecated","string_code":"CHIO-CLI-OTHER"}
suggested fix: Preserve the original message and migrate the call site to a specific registry code when touched.
exit 1

What You Can Ask

The chio receipt log is a cursor-paginated, multi-filter stream of signed decisions. Everything an agent tried to do, along with the guard that allowed or blocked it, ends up here. You can reach it three ways:

  • CLI. chio receipt list returns one JSON receipt per line (NDJSON), ideal for piping into jq, awk, or a file.
  • HTTP. GET /v1/receipts/query on the trust-control server, with a bearer token, returns a JSON envelope containing a page of receipts plus totalCount and nextCursor.
  • TypeScript SDK. ReceiptQueryClient from @chio-protocol/sdk wraps the HTTP endpoint with typed params and an async-generator paginate().

All three interfaces use the same filters. Filters combine with AND semantics, so each additional filter narrows the result set. These are the read routes, as the trust-control router registers them:

MethodPathHandler
GET/v1/receipts/queryhandle_query_receipts
GET/v1/agents/{subject_key}/receiptshandle_agent_receipts
GET/v1/receipts/analyticshandle_receipt_analytics
GET/v1/lineage/{capability_id}handle_get_lineage
GET/v1/lineage/{capability_id}/chainhandle_get_delegation_chain

/v1/lineage without a capability id is a POST that records a snapshot, so a GET against it answers 405. The two readable lineage routes both take the capability id in the path.


Filter Dimensions

Combine these filters to form an audit query. Omit a filter to leave it unconstrained.

DimensionCLI flagHTTP paramWhat it scopes
Tenant scope--tenant <id> / --admin-all(from token)Required whenever --receipt-db is used; the two flags are mutually exclusive and omitting both refuses the read rather than defaulting to all tenants. Remote --control-url reads derive scope from the bearer token and reject these two flags if supplied.
Agent(see note)agentSubjectHex-encoded Ed25519 subject public key. Resolved from receipt attribution metadata when present; otherwise via capability lineage.
Tool server--tool-servertoolServerExact match on the tool server identifier, e.g. filesystem, shell.
Tool--tool-nametoolNameExact match on the tool name within a server, e.g. write_file.
Outcome--outcomeoutcomeOne of allow, deny, cancelled, incomplete.
Capability--capabilitycapabilityIdExact capability token ID. Use this to audit the usage of a single issued token.
Time range--since / --untilsince / untilUnix seconds, inclusive on both ends.
Cost floor--min-costminCostMinimum cost_charged in minor currency units. Requires --cost-currency alongside it, and excludes receipts without financial metadata.
Cost ceiling--max-costmaxCostMaximum cost_charged in minor units. Same currency requirement and same exclusion rule as the floor.
Cost currency--cost-currencycostCurrencyISO 4217 code the cost band applies to. Mandatory whenever a floor or ceiling is set; both clap and the query builder refuse a band without it.
Page size--limitlimitResults per page. Default 50, server cap 200.
Cursor--cursorcursorLast seq value seen. Pagination is forward-only, seq-ordered.

A note on guards: there is no guard-name filter on the query endpoint, and decision.guard is not where a guard name lives. Every kernel-built deny receipt sets decision.guard to kernel, and the reason string names the pipeline rather than the guard inside it. The guard that fired appears in evidence[], as an entry whose verdict is false. Filter outcome=deny on the server, then narrow with jq over that array. The same applies to policy hashes and reason strings.

Two denials from two different guards, read three ways in one line:

receipt-audit · list-guardstranscript
$ chio --receipt-db ./receipts.db receipt list --admin-all \
  | jq -c '{tool: .tool_name, verdict: .decision.verdict,
$             guard: .decision.guard,
$             denied_by: [.evidence[]? | select(.verdict == false) | .guard_name]}'
{"tool":"run_command","verdict":"allow","guard":null,"denied_by":[]}
{"tool":"read_file","verdict":"deny","guard":"kernel","denied_by":["forbidden-path"]}
{"tool":"run_command","verdict":"deny","guard":"kernel","denied_by":["shell-command"]}
exit 0allow

For agent-scoped lookups there is also a shorter URL: GET /v1/agents/{subject_key}/receipts. It accepts only limit and cursor and is equivalent to passing agentSubject on the general query endpoint.


Common Queries Cookbook

These runnable queries cover common operator tasks. Each example assumes you have exported CONTROL_URL and CONTROL_TOKEN in your shell, or that your chio config resolves them automatically.

1. Every denial in the last 24 hours

The daily triage query. Pipe through jq to pull the essentials.

bash
SINCE=$(date -u -d '24 hours ago' +%s 2>/dev/null || date -u -v-24H +%s)

chio receipt list \
  --outcome deny \
  --since "$SINCE" \
  --control-url "$CONTROL_URL" \
  --control-token "$CONTROL_TOKEN" \
  --limit 200 \
  | jq -c '{id, ts: .timestamp, server: .tool_server, tool: .tool_name,
            guard: .decision.guard,
            denied_by: [.evidence[]? | select(.verdict == false) | .guard_name],
            reason: .decision.reason}'

guard reads kernel on every row, because that is the layer that refused. denied_by is the column that separates a forbidden path from a shell-command block. A revocation denial has no guard evidence at all: it is refused before the pipeline runs, and its reason reads capability has been revoked: <id>.

The decision object is flat

Decision is an internally tagged enum (#[serde(tag = "verdict")] at crates/core/chio-core-types/src/receipt/decision.rs), so a denial serializes as {"verdict":"deny","reason":...,"guard":...} with no nested deny object. Read .decision.guard, not .decision.deny.guard: the latter is valid jq and returns null on every row, which is the failure mode that looks like an empty result set.

2. Every file-write by agent X last week

Combine an agent subject filter with a tool server and tool name. Works equally well for any server and tool pair. The subject key is the hex string a receipt carries at .metadata.attribution.subject_key; read one off the CSV export below rather than typing it, since it is 64 characters and a single wrong nibble matches nothing silently.

bash
AGENT=7b0f6f631f6e66207140ead0b6b2e9418916d2c4b3c7448ba5f7ed27f5c8d038
START=$(date -u -d '7 days ago' +%s 2>/dev/null || date -u -v-7d +%s)
END=$(date -u +%s)

chio receipt list \
  --tool-server filesystem \
  --tool-name write_file \
  --since "$START" \
  --until "$END" \
  --limit 200 \
  --control-url "$CONTROL_URL" \
  --control-token "$CONTROL_TOKEN" \
  | jq --arg a "$AGENT" -c 'select(.metadata.attribution.subject_key == $a)'

The server also accepts agentSubject directly over HTTP. The jq form above is useful when you want to see only the receipts with explicit attribution metadata, as opposed to those resolved via capability lineage. It reads one page: 200 is the server cap, so if the week holds more than that, wrap it in the cursor loop from the pagination section below.

3. Everything blocked by the secret-leak guard

Guard names live inside the decision payload, so filter outcome=deny server-side, then narrow on the guard name.

bash
chio receipt list \
  --outcome deny \
  --limit 200 \
  --control-url "$CONTROL_URL" \
  --control-token "$CONTROL_TOKEN" \
  | jq -c 'select(any(.evidence[]?; .guard_name == "secret-leak" and .verdict == false))
           | {id, ts: .timestamp, server: .tool_server, tool: .tool_name, reason: .decision.reason}'

Swap secret-leak for any guard the runtime can register. These are the names guard.name() returns, which is what lands in guard_name. They are kebab-case, and they are not the snake_case rule-block keys you write in a policy: the secret_patterns block compiles to the secret-leak guard, and path_allowlist compiles to path-allowlist.

agent-velocity, behavioral-sequence, browser-automation, code-execution, computer-use, content-review, data-flow, egress-allowlist, embedding-anomaly, forbidden-path, input-injection-capability, internal-network, jailbreak, mcp-tool, memory-governance, patch-integrity, path-allowlist, prompt-injection, remote-desktop-side-channel, response-sanitization, secret-leak, shell-command, velocity. A custom guard you built with Custom Guards reports the name it registers under.

4. Budget-exceeded events for a workload

A budget refusal is a kernel denial, not a guard one, so there is no guard name to select on. What separates it is the financial metadata block: the kernel attaches it on a monetary deny with cost_charged zeroed and attempted_cost set to what the call would have spent. Select on the block existing, and read the reason for which limit was hit.

bash
chio receipt list \
  --capability cap-workload-xyz \
  --outcome deny \
  --limit 200 \
  --control-url "$CONTROL_URL" \
  --control-token "$CONTROL_TOKEN" \
  | jq -c 'select(.metadata.financial.attempted_cost != null)
           | {id,
              ts: .timestamp,
              reason: .decision.reason,
              attempted: .metadata.financial.attempted_cost,
              remaining: .metadata.financial.budget_remaining,
              total: .metadata.financial.budget_total,
              currency: .metadata.financial.currency}'

The reason string for an exhausted invocation budget reads invocation budget exhausted for capability <id>. A deny that never matched a monetary grant carries no financial block at all, which is why the selector tests for the field rather than comparing a guard name.

5. Top 10 tools by cost this month

Do not aggregate this yourself. The analytics endpoint already groups by tool for exactly this shape of question. Call it with a month-wide since.

bash
MONTH_START=$(date -u -d "$(date -u +%Y-%m-01)" +%s 2>/dev/null \
  || date -u -j -f "%Y-%m-%d" "$(date -u +%Y-%m-01)" +%s)

curl -sS \
  -H "Authorization: Bearer $CONTROL_TOKEN" \
  "$CONTROL_URL/v1/receipts/analytics?since=$MONTH_START&timeBucket=day&groupLimit=10" \
  | jq '.byTool
        | sort_by(-.metrics.totalCostCharged)
        | .[:10]
        | map({server: .toolServer, tool: .toolName, cost: .metrics.totalCostCharged, calls: .metrics.totalReceipts})'

totalCostCharged is in minor currency units, so divide by 100 for USD dollars when reporting. Both analytics calls need admin read authority: a tenant-scoped token gets 403 rather than a narrowed result. complianceRate and reliabilityScore are omitted from a row whose denominator is zero, so read them with a // default.

6. Per-agent spend summary

The same analytics endpoint groups by agent. Pair with a time range for monthly reports.

bash
curl -sS \
  -H "Authorization: Bearer $CONTROL_TOKEN" \
  "$CONTROL_URL/v1/receipts/analytics?since=$MONTH_START&until=$MONTH_END&groupLimit=50" \
  | jq '.byAgent
        | map({agent: .subjectKey,
               calls: .metrics.totalReceipts,
               spend_minor: .metrics.totalCostCharged,
               denies: .metrics.denyCount,
               compliance: .metrics.complianceRate})
        | sort_by(-.spend_minor)'

7. Trace a capability token's full usage lineage

When an incident implicates a specific capability, pull every receipt it produced, then resolve its delegation chain from /v1/lineage/{capability_id}/chain. Together they answer what this token did, and who issued it to whom.

bash
CAP=cap-abc123

# Every receipt this capability produced, in order.
chio receipt list \
  --capability "$CAP" \
  --limit 200 \
  --control-url "$CONTROL_URL" \
  --control-token "$CONTROL_TOKEN" \
  > receipts-$CAP.ndjson

# The delegation chain (root to leaf).
curl -sS \
  -H "Authorization: Bearer $CONTROL_TOKEN" \
  "$CONTROL_URL/v1/lineage/$CAP/chain" \
  | jq '.[] | {subject: .subject_key, issuer: .issuer_key, depth: .delegation_depth, expires: .expires_at}'

The lineage chain is the one response on this API whose keys are snake_case: CombinedDelegationChainEntry carries no rename_all, so subject_key and delegation_depth are the literal wire names. Do not camelCase them to match the query endpoint.


SDK Cookbook

An internal dashboard, a compliance bot, or a nightly summary reaches for a client instead of the CLI. Two ship today, one per ecosystem, and they are the same object: a constructor taking a control-plane URL and a bearer token, a query for one page, and a paginate that walks the cursor for you. The filter names are the camelCase forms from the wire, identical in both.

bash
$ npm install @chio-protocol/sdk
$ pip install chio-sdk

The Python distribution chio-sdk installs the import package chio. Count the matches first, then walk every page:

@chio-protocol/sdkchio-sdk
import { ReceiptQueryClient } from "@chio-protocol/sdk";

const client = new ReceiptQueryClient(
  process.env.CONTROL_URL!,
  process.env.CONTROL_TOKEN!,
);

// How many receipts match, without fetching them all.
const head = await client.query({
  outcome: "deny",
  since: Math.floor(Date.now() / 1000) - 86_400,
  limit: 1,
});
console.log(`${head.totalCount} denials in the last 24 hours`);

// Walk every matching page with the async generator.
let total = 0;
for await (const page of client.paginate({
  toolServer: "filesystem",
  toolName: "write_file",
  since: Math.floor(Date.now() / 1000) - 7 * 86_400,
})) {
  for (const receipt of page) {
    total += 1;
    // decision is optional: a trace or advisory receipt carries none.
    if (receipt.decision?.verdict === "deny") {
      console.log(receipt.id, receipt.decision.guard, receipt.decision.reason);
    }
  }
}
console.log(`${total} file-writes this week`);

Both clients speak GET /v1/receipts/query with an Authorization: Bearer header, and both encode filters as query parameters in the order the caller wrote them. The Python block above issues three requests: the counting call, the first page, and the same page filters plus the cursor the first response returned.

requested URLs, python runtext
/v1/receipts/query?outcome=deny&since=1785521720&limit=1
/v1/receipts/query?toolServer=filesystem&toolName=write_file
/v1/receipts/query?toolServer=filesystem&toolName=write_file&cursor=42

The TypeScript tab passes an extra since to paginate, so its last two URLs carry that parameter as well. nextCursor is a number, and both clients require it to strictly advance: a server that repeats a cursor or hands one back that moves backwards raises rather than looping forever. Python holds the full set of cursors it has seen; TypeScript compares against the previous one.

The same query straight over HTTP, so you can see the envelope the SDKs unwrap. This is the deny lane of a store holding one allow and two denials:

receipt-audit · query-httptranscript
$ curl -sS -H "Authorization: Bearer $CONTROL_TOKEN" \
    "$CONTROL_URL/v1/receipts/query?outcome=deny&limit=1" \
  | jq '{totalCount, nextCursor, receipts: [.receipts[] | {tool_name, decision}]}'
{
  "totalCount": 2,
  "nextCursor": 2,
  "receipts": [
    {
      "tool_name": "read_file",
      "decision": {
        "guard": "kernel",
        "reason": "guard denied the request: guard \"guard-pipeline\" denied the request",
        "verdict": "deny"
      }
    }
  ]
}
exit 0deny

totalCount is 2 while the page holds 1, because the count ignores limit and cursor. And decision comes back flat, with guard reading kernel and the reason naming the pipeline.

For a single-agent view, build the client once and narrow the generator with agentSubject on every call. Each SDK also separates a rejected query from a dropped connection, so a 401 and a dead socket are different except branches: QueryError and TransportError in TypeScript, ChioQueryError and ChioTransportError in Python.

@chio-protocol/sdkchio-sdk
import { QueryError, TransportError } from "@chio-protocol/sdk";

try {
  const { totalCount, receipts } = await client.query({
    agentSubject: "7b0f6f63...",
    outcome: "deny",
    limit: 50,
  });
  console.log(`${totalCount} total denials for this agent`);
  for (const r of receipts) {
    console.log(r.timestamp, r.tool_server, r.tool_name, r.decision);
  }
} catch (err) {
  if (err instanceof QueryError) {
    console.error("Control plane rejected the query", err.status, err.message);
  } else if (err instanceof TransportError) {
    console.error("Could not reach the control plane", err.message);
  } else {
    throw err;
  }
}

Run against a control plane that rejects the bearer token, then against one that is not listening, the Python branch prints:

stdoutbash
Control plane rejected the query 401 receipt query failed with status 401
Could not reach the control plane failed to fetch receipts

Paginating Large Result Sets

Pagination is cursor-based and forward-only. The server assigns each receipt a monotonically increasing seq and returns the last seq of each page as nextCursor. Pass that value back as cursor on the next call to pick up where you left off.

  • Default page size. 50.
  • Server cap. 200. Requesting more is silently clamped.
  • End-of-stream. A response without nextCursor (or with it set to null) means you have reached the last page for the current filter set.
  • Total count. totalCount reflects all matches for the filters, independent of limit and cursor. Use it to show "N total" in a UI without walking every page.

Retrieving every matching page from a shell has one trap in it.

A receipt line carries no seq

seq is the store's row number, not a receipt field, so it is never serialized into the JSON Lines stream. The CLI writes the cursor on stderr instead, as next_cursor=<seq> total_count=<n>. A loop that reads .seq off the last line gets null on page one and stops there, having collected a single page of what it promised to collect in full.
receipt-audit · list-remote-cursortranscript
$ chio --control-url "$CONTROL_URL" --control-token "$CONTROL_TOKEN" \
  receipt list --limit 2 2>cursor.err 1>page.ndjson
$ echo "stdout: $(wc -l < page.ndjson) receipt lines"
$ echo "stderr: $(cat cursor.err)"
stdout: 2 receipt lines
stderr: next_cursor=2 total_count=3
exit 0

So the loop splits the two streams and reads the cursor out of stderr:

receipt-audit · paginatetranscript
$ cursor=""
$ : > all-denies.ndjson
$ while : ; do
$     args=(--outcome deny --limit 1 --control-url "$CONTROL_URL" --control-token "$CONTROL_TOKEN")
$     [ -n "$cursor" ] && args+=(--cursor "$cursor")
$ 
$     # Receipts go to stdout; "next_cursor=<seq> total_count=<n>" goes to stderr.
$     page=$(chio receipt list "${args[@]}" 2>page.err)
$     [ -n "$page" ] && printf '%s\n' "$page" >> all-denies.ndjson
$ 
$     cursor=$(sed -n 's/^next_cursor=\([0-9]*\).*/\1/p' page.err)
$     [ -z "$cursor" ] && break
$   done
$ rm -f page.err
$ wc -l < all-denies.ndjson
2
exit 0

Two denials, collected a page at a time, with the loop stopping on the request that comes back short and therefore carries no next cursor. Over HTTP the cursor is in the response envelope instead, as nextCursor, and no stream juggling is needed.

New receipts keep arriving

The receipt log is append-only. If new receipts land while you are walking pages, the cursor still advances correctly: you will see those receipts on subsequent pages because their seq is greater than your current cursor. There is no re-shuffling and no risk of missing history.

Exporting for Audit

Export queries as files for audit and downstream systems. Chio supports three formats and delivery paths.

NDJSON (one receipt per line)

The default output of chio receipt list. Stable, streamable, and the native input format of most log tooling. Redirect straight to a file:

bash
chio receipt list \
  --since "$QUARTER_START" --until "$QUARTER_END" \
  --limit 200 \
  --control-url "$CONTROL_URL" --control-token "$CONTROL_TOKEN" \
  > 2026-Q1-receipts.ndjson

CSV for spreadsheets

Pair NDJSON with jq -r to flatten into CSV. Pick only the columns you need; auditors rarely want the full receipt JSON.

bash
echo 'id,timestamp,server,tool,outcome,cost_charged,agent' > receipts.csv

chio receipt list \
  --since "$MONTH_START" --limit 200 \
  --control-url "$CONTROL_URL" --control-token "$CONTROL_TOKEN" \
  | jq -r '[.id,
            .timestamp,
            .tool_server,
            .tool_name,
            (.decision.verdict // ""),
            (.metadata.financial.cost_charged // 0),
            (.metadata.attribution.subject_key // "")] | @csv' \
  >> receipts.csv

The outcome column is .decision.verdict, with a // default because a trace or advisory receipt has no decision at all. Do not reach for (.decision | keys[0]): the decision is internally tagged, so that yields the literal string verdict on an allow and guard on a deny, never an outcome. The same run against a local store:

receipt-audit · csvtranscript
$ echo 'id,timestamp,server,tool,outcome,cost_charged,agent'
$ chio --receipt-db ./receipts.db receipt list --admin-all \
  | jq -r '[.id, .timestamp, .tool_server, .tool_name,
$             (.decision.verdict // ""),
$             (.metadata.financial.cost_charged // 0),
$             (.metadata.attribution.subject_key // "")] | @csv'
id,timestamp,server,tool,outcome,cost_charged,agent
"ed6c03fe092c83e54898702714592bc9e196a4120f58616840259b4bf4ce372f",1788610104,"*","run_command","allow",0,"ee7d2c513773e84d23ea9ade9bf0f7ffd9cd0c6c4545135ad0a441d1cb607fc3"
"f0dea3dd23ccc35f7a15f6e753b3a095ba04ffa1f4fed79b1593f05666cc0736",1788610105,"*","read_file","deny",0,"e7e09911a3f1f8fbe85d5fe2859dcf4a3ffb56e792e79f097a609e2b9845486c"
"406ad0272531e42e4fee6a503fd24e6a0ba8cc705db01852dcaf5fe0de9e227e",1788610107,"*","run_command","deny",0,"8aaa4bfda187af829dcfebd3d702fee1483a0eaaa22bf249be7ee60b3686ac6a"
exit 0

Forwarding to a SIEM

Ad-hoc exports are fine for quarterly reviews. For continuous compliance, stream receipts into Splunk, Elasticsearch, or whatever your SOC already runs. The chio-siem crate is the library for that: its ExporterManager opens a read-only connection to the receipt database, pulls with its own seq cursor, and fans out to registered exporters with backoff and a bounded dead-letter queue. It has no binary and no CLI verb, so a deployment embeds it. See SIEM Export for setup instructions.

Offline evidence packages

For bilateral audit with an external counterparty, use chio evidence export. It produces a directory containing the filtered receipts, the capability lineage, and any checkpoint roots needed to prove log integrity, plus a manifest that hashes every one of them. Pass --policy-file to add the policy source and its metadata; the package does not carry a policy otherwise. The counterparty can verify it with chio evidence verify --input ./pkg on an air-gapped machine.


Failures and Recovery

What you seeWhat it meansWhat to do
--tenant <id> or --admin-all is required for local receipt readsA --receipt-db read named no boundaryAdd one. The read fails closed rather than defaulting to every tenant
receipt list read-boundary flags apply to local --receipt-dbA --control-url read passed --tenant or --admin-allDrop the flag. The control token already carries the scope
the following required arguments were not provided: --cost-currencyA cost band with no currencyAdd --cost-currency USD. The query builder refuses the band a second time server-side
An empty result where you expected rowsOften a jq path that is valid and always null: .decision.deny.guard, or a guard name compared against .decision.guardPrint one raw row first. Guard names live in .evidence[].guard_name
A page that stops after 200 rows--limit above the cap is clamped silently, with no warning and exit 0Page with the cursor loop. Compare what you collected against total_count on stderr
HTTP 403 on /v1/receipts/analyticsThe token is tenant-scoped and analytics needs admin read authorityUse an admin token, or aggregate from receipt list yourself
HTTP 405 on GET /v1/lineageThat path is the POST that records a snapshotUse /v1/lineage/{capability_id} or its /chain suffix
receipt-audit · list-remote-tenanttranscript
$ chio --control-url "$CONTROL_URL" --control-token "$CONTROL_TOKEN" \
  receipt list --tenant acme
error [urn:chio:error:cli:other]: receipt list read-boundary flags apply to local --receipt-db; remote reads derive scope from the control token
context: {"domain":"cli","severity":"error","stability":"deprecated","string_code":"CHIO-CLI-OTHER"}
suggested fix: Preserve the original message and migrate the call site to a specific registry code when touched.
exit 1
receipt-audit · list-cost-bandtranscript
$ chio --receipt-db ./receipts.db receipt list --admin-all --min-cost 0
error: the following required arguments were not provided:
  --cost-currency <COST_CURRENCY>

Usage: chio receipt list --cost-currency <COST_CURRENCY> --admin-all --min-cost <MIN_COST>

For more information, try '--help'.
exit 2

Composing with Verification

Querying finds receipts; verification checks their signatures and log evidence. Query the receipts of interest, then verify that subset offline against the issuer's public key and the log's checkpoint roots.

A typical audit workflow:

  • Query the log with the filters for your incident or reporting window. Save the NDJSON.
  • Export an evidence package covering the same window (or the same capability ID) so the checkpoint roots travel with the receipts.
  • Run offline verification against the package. Any tampered or missing receipt fails the verifier.

For the verification mechanics, see Verify Receipts Offline. If you are building a dashboard that does both live queries and evidence checks, the Receipt Dashboard reference shows how the operator console wires them together.


Flag Summary

Reference flags for chio receipt list.

FlagPurpose
--capabilityScope to a single capability token.
--tool-server / --tool-nameScope to a server and optionally a tool within it.
--outcomeallow · deny · cancelled · incomplete.
--since / --untilTime window in Unix seconds, inclusive.
--min-cost / --max-costCost band in minor currency units. Either one requires --cost-currency.
--cost-currencyISO 4217 code the cost band applies to. Mandatory alongside a floor or a ceiling.
--limit / --cursorPage size (default 50, cap 200) and resume seq.
--control-url / --control-tokenTrust-control server URL and bearer token.
--receipt-dbLocal SQLite path for offline mode.
--tenant / --admin-allTenant read boundary, required with --receipt-db (mutually exclusive). Omitting both refuses the read; remote --control-url mode rejects them.

Next Steps