Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

Read request coverage and freshness

Tell a request not yet indexed from a missing one, read how current an answer is, and retry or refresh without resending traffic.

How-to guideLast updated

On this page

Your application got a 200 and an x-tt-request-id a few seconds ago, and the receipt read says the service cannot find it. Or a dashboard’s token total looks low for a busy hour. Both are the same question: how far has the hosted index caught up with the traffic your gateways served, and is this answer complete?

This page shows where each hosted answer states that, how to tell “not indexed yet” from “not here”, and which of retry, refresh or a different question fixes it. It uses the tokentriage cloud CLI; the SDKs return the same documents.

Read Trace one request from its ID first if you have not read a receipt before.

What you need

You need Because
your organization ID (org_…) and workspace ID (t_…) every read here is scoped to one workspace
a management key in TOKENTRIAGE_MANAGEMENT_KEY with metadata.read it gates every read on this page
cost.read, if you want money figures without it, money cells read unavailable (scope_not_authorized)

You do not need operator access to the analytics service, a log file, or a runtime key. Nothing on this page sends a model request.

Runtime success is not index success

A gateway answers your application first and records the request afterwards. The gateway writes the request to its journal, the journal ships to the analytics service, and only then can customer.requests.get or an insight query see it. A 200 with a request ID proves the provider answered. It says nothing yet about whether the hosted index has the row.

So the first read of a fresh request can miss, and a window that ends “now” can undercount. Neither means the request was lost. Resending the model call to “fix” a read creates a second, paid request and leaves the first one exactly where it was.

1. Read the read context

Every windowed answer (insights overview, insights drilldown) carries a read_context: the conditions the answer was computed under. Ask with --json to see it:

$ tokentriage cloud insights overview \
    --metric requests.v1 --metric provider_spend.v1 \
    --metric cost_per_1000_requests.v1 --metric tokens.v1 \
    --metric response_latency.v1 \
    --org org_K11111111111 --workspace t_K11111111111 \
    --from 2026-09-20T12:00:00Z --to 2026-09-21T12:00:00Z \
    --base-url http://127.0.0.1:48252 --json
"read_context": {
  "from": "2026-09-20T12:00:00Z",
  "to": "2026-09-21T12:00:00Z",
  "arrival_fence": "918273",
  "watermarks": [
    {
      "producer_class": "gateway_journal",
      "produced_through": "918290",
      "indexed_through": "918273",
      "last_indexed_at": "2026-09-21T11:58:30Z"
    },
    {
      "producer_class": "control_plane_outbox",
      "produced_through": "41902",
      "indexed_through": "41902",
      "last_indexed_at": "2026-09-21T11:59:48Z"
    }
  ],
  "evaluated_at": "2026-09-21T12:00:00Z"
}

That is the read_context from the command’s output, trimmed: the full block also carries the account, workspace, scope digest, catalog and filter versions, purposes, and the authority, projection, retention and exclusion generations. The command ran against a loopback server replaying the recorded overview_unpriced_spend case in sdk/spec/insight-read-fixtures.json, so the port and every value are that recording’s.

Read it in this order:

  • arrival_fence is the position in the ingest arrival counter this answer read up to. Every row that arrived after it is outside the answer, even if its request happened inside the window. It is a counter, not a time.
  • watermarks gives one entry per producer class. gateway_journal is request traffic from your gateways; control_plane_outbox is control-plane events such as releases and app feedback. produced_through is how far the producer has sent, indexed_through how far the service has stored. Here the journal is 17 positions behind.
  • last_indexed_at is when that class last indexed anything. A class with nothing indexed has unmeasured freshness, even if both counters read zero.
  • evaluated_at is when the answer was computed.

A gap between produced_through and indexed_through is lag, not loss: those rows are on their way. produced_through can itself be a floor, so a gap of one can mean “at least one more”.

Without --json, the CLI states the same facts on stderr so a pipeline keeps working. The same replay without --json:

application	requests.v1	1284 count
application	provider_spend.v1	unavailable (unpriced_population)
application	cost_per_1000_requests.v1	unavailable (unpriced_population)
application	tokens.v1	84210773 tokens (FLOOR: ingest_gap:gateway_journal)
application	response_latency.v1	412 milliseconds
cloud: customer.insights.query POST http://127.0.0.1:48251/portal/api/v1/customer/v1/organizations/org_K11111111111/workspaces/t_K11111111111/insights/query
cloud: coverage is incomplete for this window (ingest_gap:gateway_journal); measured values use an incomplete population; counts/sums are lower bounds, ratios/percentiles may move either way
cloud: gateway_journal is behind (produced 918290, indexed 918273), so the most recent traffic may be missing

2. Read the coverage reasons

Coverage is stated in two places, with two small vocabularies.

On a windowed answer, coverage lists why the population is incomplete, such as ingest_gap:gateway_journal above. A cell computed over that population comes back partial, with the number and the same coverage list:

  • counts and sums (requests.v1, tokens.v1, spend) are lower bounds: the true value is at least this;
  • ratios and percentiles (cost per request, latency quantiles) are not bounds: they can move up or down as missing rows arrive.

On a request receipt, coverage.unstated names members the answer could not fill, each with one of three reasons:

reason means
not_projected the hosted store has no column for this member at all, for any request
not_observed the column exists and holds nothing for this request
not_authorized the member was withheld for want of a grant

A receipt’s coverage describes that one record. It does not say whether the index as a whole is current; the read context on a windowed answer does.

3. Tell “not indexed yet” from “not found”

A by-ID read has no window of its own, so the hosted service searches the last 90 days of indexed arrivals for it. When it finds nothing, it does not answer “no such request”, because an arrival counter cannot prove a request never happened. The answer is:

  • HTTP 503, type=dependency_unavailable, with a Retry-After header (30 seconds on this build), and a detail that the request “has not been projected into the analytics store yet”, naming the arrival position searched and the date range scanned.

not_found (HTTP 404) is reserved for two cases: the workspace in the path is not one your key can see, or the record the index returned belongs to a different workspace than the path.

An ID from another workspace gets the same retryable 503 as an ID that is not indexed yet. That is concealment: a different answer would confirm the ID exists somewhere. So use time to tell them apart:

Situation Likely cause What to do
a request from the last few minutes misses, then appears index lag retry after Retry-After
misses persist over several intervals while a current window’s gateway_journal watermark shows no gap wrong workspace, or a mistyped ID check the org, workspace and ID against the ticket
the ticket is older than 90 days outside the searched range, which the detail names the by-ID read cannot reach it

Not yet executed against a deployment; output omitted.

$ tokentriage cloud request get \
    --org $TT_ORG --workspace $TT_WORKSPACE --request $TT_REQUEST
Where the miss wording on this page comes from

The status, type, 30-second interval, 90-day range and detail wording were read from internal/control/cmd/controlplaned/foundation_insights.go and internal/central/analytics/chquery/request.go at this page’s date. No miss was captured for this page, so the exact text your deployment prints may differ.

4. Retry or refresh

Each refusal names its remedy, and they are not interchangeable.

Answer Remedy What changes
HTTP 503 dependency_unavailable with Retry-After wait the interval, then send the same read nothing but time; do not loop faster than the header asks
HTTP 503 coverage_unknown retry once the ingest frontier has moved the service could not bound what is missing, so it gave no figure at all
HTTP 410 read_context_expired drop the read_context and ask again (refresh_view) a new snapshot: counts may differ from the page you were reading
HTTP 410 snapshot_expired start a new query the same, for a paged read

coverage_unknown from the CLI, replaying the recorded coverage_unknown_503 case:

$ tokentriage cloud insights overview --metric requests.v1 \
    --org org_K11111111111 --workspace t_K11111111111 \
    --from 2026-09-20T12:00:00Z --to 2026-09-21T12:00:00Z \
    --base-url http://127.0.0.1:48253
Error: customer.insights.query was refused (HTTP 503); detail: the ingest side cannot bound what is missing for this window; retry once the frontier has moved; type=coverage_unknown; request_id=req_coverage_unknown_503

The command exited with status 1 and printed nothing on stdout, so a script cannot mistake the refusal for an empty window.

A read context stays valid for five minutes, and any change to the facts it pinned (your grants, the projection, retention or exclusions) expires it early. When you page through a drill-down, or pass one overview’s read_context to a follow-up drill-down so both describe the same population, a read_context_expired means start again from a fresh overview. Do not splice pages from two contexts into one table.

5. Read unknown cost and usage

A missing figure has a reason, and the reason says who can fix it.

You see It means It is not
receipt cost.state: unpriced the request ran; no price has settled for it yet $0.00
receipt cost.state: unavailable, reason: scope_not_authorized you lack cost.read; the figure exists a failed read
cell unavailable (unpriced_population) some rows in the window carry no price, so a sum would understate spend zero spend
receipt usage: null with usage_source: unavailable the provider reported no usage, often because nothing was dispatched zero tokens
attempt row usage_known: false this dispatch’s token counts are zeros that mean nothing a free attempt
cell partial with ingest_gap:… a floor over an incomplete population the total

A figure that fills in later is the same request being priced or indexed, not new spend.

What can go wrong

You read a window that ends now. The newest minutes are the ones most likely to be behind the fence. For a reconciliation, choose a window that ended a while ago and check that the gateway_journal watermark shows no gap.

You combine a partial count with a complete one. A floor plus a figure is a floor. Keep the coverage list with any number you copy into a report.

The data was excluded. Traffic under a deletion’s exclusion leaves every read at once, and a population where every row is excluded answers unavailable (excluded) rather than zero. Export or delete data covers how exclusion and purge proceed.

There is no local-log fallback for the hosted service: when the index is behind, the remedy is to wait for it, not to read a file on the gateway host. This page publishes no indexing latency bound, because none has been measured and recorded for customers yet.