Gateway insight reads
The insight operations, the metrics and units they answer in, the states a value can carry, and every refusal code.
On this page
This is the read contract behind a hosted gateway’s traffic figures: five operations, the document they take, the measurement catalogue they answer from, the three states a measure can be in, and the closed refusal table. Reads only; no operation here writes anything.
Field names and values below are taken from sdk/spec/insight-read-fixtures.json, the
recording the lane’s own handler wrote, and from the measurement catalogue that same
recording carries. Read your gateway’s traffic and spend
is the task-shaped version of this page.
Operations
All five share the customer base path /portal/api/v1/customer/v1/. The organization and
workspace come from the path; a body that names different ones is turned away rather than
rewritten. None of them takes an Idempotency-Key: a first read mints a read context, so
a repeat is a different answer to the same question.
| Operation | Method and path | TypeScript | Python | CLI |
|---|---|---|---|---|
customer.insights.query |
POST /organizations/{org}/workspaces/{ws}/insights/query |
insights.query |
insights.query |
cloud insights overview, cloud insights drilldown |
customer.insights.catalog |
GET /organizations/{org}/insights/catalog |
insights.catalog |
insights.catalog |
cloud insights catalog |
customer.saved_views.list |
GET /organizations/{org}/workspaces/{ws}/saved-views |
savedViews.list |
saved_views.list |
cloud view list |
customer.saved_views.get |
GET /organizations/{org}/saved-views/{sv} |
savedViews.get |
saved_views.get |
cloud view get |
customer.saved_views.open |
POST /organizations/{org}/saved-views/{sv}/open |
savedViews.open |
saved_views.open |
cloud view open |
Saved-view creation, editing and deletion are supported by all three clients; see
Save a gateway view. Runtime feedback submission uses
the separately authorized gateway endpoint; see App feedback.
saved_views.open sends no body: its current authority comes from the credential.
Every read needs metadata.read on the scope it asks about. A metric whose row says so
also needs cost.read; without it that cell reads unavailable{scope_not_authorized} and
the rest of the answer still arrives.
The query document
insight-query/v1, the body of customer.insights.query.
| Field | Type | Notes |
|---|---|---|
schema |
integer | 1 |
shape |
enum | overview, series, facets, drilldown, markers |
scope |
object | organization_id, workspace_id, application_ids, gateway_ids, environment_ids |
from, to |
RFC 3339 | half-open UTC window, [from, to) |
grain |
enum | hour, day, week, month; required for series, absent elsewhere |
display_tz |
IANA zone | labels buckets; the arithmetic stays UTC |
purposes |
list | defaults to ["application"] server-side |
release_ids |
list of release ids | optional; match any listed release within the existing scope; absent or empty means no additional release narrowing |
metrics |
list | versioned ids such as provider_spend.v1; absent on drilldown and markers |
facets |
list | members of the closed facet set, for the facets shape |
drilldown |
enum | request or attempt, for the drilldown shape; feedback is registered, see feedback rows |
feedback_filter |
object | feedback grain only: logical_request_id, match |
read_context |
object | absent on the first read of a view, echoed unchanged after it |
cursor, page_size |
string, integer | paging for row shapes |
A release filter applies to all query shapes and is part of the read-context identity. Changing it requires a fresh read context. It grants no access to another application or environment, and a saved view rechecks the opener’s current authority.
The scope narrows the ceiling the caller’s bindings already resolved and cannot widen it.
An empty list means “every descendant I may read”. Naming an application outside the
caller’s bindings answers 403 forbidden; another organization is concealed.
The closed enums, as the catalogue publishes them:
| Set | Members |
|---|---|
| shapes | overview, series, facets, drilldown, markers |
| grains | hour, day, week, month |
| purposes | application, playground, comparison, judge, connection_validation |
| facets | final_model, route, outcome, release, connection, purpose, input_size_bucket |
| units | count, ratio, nano_usd, tokens, milliseconds, seconds, marker, coverage |
Purposes answer as separate groups. A query naming two of them returns one group per purpose rather than a blended figure.
The measurement catalogue
customer.insights.catalog answers from the server’s build and reaches no warehouse, so
it returns the same document on a deployment whose analytics producer is switched off.
Each row carries the question it answers, its unit, its population, its aggregation, the
rule for a missing value, the actions reading it needs, and whether anything writes it in
this build.
| Metric | Unit | Population | Aggregation | Missing rule | Needs | Producer |
|---|---|---|---|---|---|---|
requests.v1 |
count |
requests | count_distinct | coverage_unknown |
metadata.read |
produced |
outcomes.v1 |
count |
requests | count | coverage_unknown |
metadata.read |
produced |
completion_rate.v1 |
ratio |
requests | ratio | denominator_zero |
metadata.read |
produced |
provider_spend.v1 |
nano_usd |
cost_effects | sum | unpriced_population |
metadata.read + cost.read |
produced |
cost_per_1000_requests.v1 |
nano_usd |
cost_effects | ratio | denominator_zero |
metadata.read + cost.read |
produced |
cost_per_completed.v1 |
nano_usd |
cost_effects | ratio | denominator_zero |
metadata.read + cost.read |
produced |
tokens.v1 |
tokens |
attempts | sum | coverage_unknown |
metadata.read |
produced |
response_latency.v1 |
milliseconds |
requests | quantile | not_observed |
metadata.read |
produced |
first_output_latency.v1 |
milliseconds |
requests | quantile | not_observed |
metadata.read |
produced |
route_mix.v1 |
ratio |
requests | share | denominator_zero |
metadata.read |
produced |
model_mix.v1 |
ratio |
requests | share | denominator_zero |
metadata.read |
produced |
release_annotations.v1 |
marker |
release_markers | list | coverage_unknown |
metadata.read |
produced |
app_feedback.v1 |
count |
feedback | count | not_observed |
metadata.read |
produced |
app_feedback_rated_requests.v1 |
count |
feedback | count_distinct | not_observed |
metadata.read |
produced |
activity_spend.v1 |
nano_usd |
cost_effects | sum | unpriced_population |
metadata.read + cost.read |
produced |
team_runs.v1 |
count |
requests | count_distinct | coverage_unknown |
metadata.read |
produced |
freshness_lag.v1 |
seconds |
read_context | latest | coverage_unknown |
metadata.read |
produced |
coverage.v1 |
coverage |
read_context | latest | coverage_unknown |
metadata.read |
produced |
protection.v1 |
count |
protection_evaluations | count | not_observed |
metadata.read |
produced |
test_quality.v1 |
ratio |
none | none | no_producer |
metadata.read |
no_producer |
paired_reduction.v1 |
ratio |
none | none | no_producer |
metadata.read + cost.read |
no_producer |
cohort_projection.v1 |
nano_usd |
none | none | unpriced_population |
metadata.read + cost.read |
produced_elsewhere |
budget_standing.v1 |
nano_usd |
none | none | unpriced_population |
metadata.read + cost.read |
produced_elsewhere |
attention_rules.v1 |
count |
none | none | no_producer |
metadata.read |
no_producer |
saved_view.v1 |
count |
none | none | scope_not_authorized |
metadata.read |
produced_elsewhere |
A metric whose producer is no_producer may be named in a query. Its cell answers
unavailable{no_producer}, which is how a metric picker can offer the question while
showing that nothing writes it yet.
produced_elsewhere says the opposite: the fact IS recorded, by the control plane rather
than by the analytics projection, and is read through that producer’s own operations —
saved_view.v1 through the saved-view operations — so an insight query answers
unavailable{no_producer} for it while a picker must not grey it out.
Model and route shares count each logical request once at its final served model/route, including an unserved group. Fallback attempts remain separate in attempt usage and spend. Unit-cost ratios are unavailable when cost, attribution or the request population is incomplete; the known provider-spend subtotal remains visible with its coverage disclosure.
Ratio metrics carry their numerator and denominator on the catalogue row:
completion_rate.v1 is requests whose terminal status is succeeded over requests admitted
in the window, and cost_per_1000_requests.v1 is settled spend in the window times one
thousand over requests admitted in the window. Latency metrics store the figures the
producer measured in milliseconds and are not recomputed from stored instants; a
non-streaming request contributes no first_output_latency.v1 observation.
The full catalogue as the CLI prints it, with each metric's question
requests.v1 count produced metadata.read How many requests did this application make?
outcomes.v1 count produced metadata.read How did those requests end?
completion_rate.v1 ratio produced metadata.read What share of requests produced a usable answer?
provider_spend.v1 nano_usd produced metadata.read + cost.read What did this application's upstream usage cost?
cost_per_1000_requests.v1 nano_usd produced metadata.read + cost.read What does a thousand requests cost?
cost_per_completed.v1 nano_usd produced metadata.read + cost.read What does a request that actually worked cost?
tokens.v1 tokens produced metadata.read How many tokens did the upstreams read and write?
response_latency.v1 milliseconds produced metadata.read How long did a request take end to end?
first_output_latency.v1 milliseconds produced metadata.read How long until the first token reached the caller?
route_mix.v1 ratio produced metadata.read Which routes served this traffic, and in what proportion?
model_mix.v1 ratio produced metadata.read Which models answered, and in what proportion?
release_annotations.v1 marker produced metadata.read When did a configuration release take effect here?
app_feedback.v1 count produced metadata.read What did the application's own users say about these answers?
app_feedback_rated_requests.v1 count produced metadata.read How many requests admitted in this window have app-reported feedback?
activity_spend.v1 nano_usd produced metadata.read + cost.read What did each kind of activity cost?
team_runs.v1 count produced metadata.read How much of this traffic was the team's own work rather than the application's?
freshness_lag.v1 seconds produced metadata.read How far behind live is this view?
coverage.v1 coverage produced metadata.read What might be missing from this view, and how much?
protection.v1 count produced metadata.read How many protection component phases were evaluated?
test_quality.v1 ratio no_producer metadata.read How well did this configuration do on the saved tests?
paired_reduction.v1 ratio no_producer metadata.read + cost.read How much cheaper was the new configuration on the same work?
cohort_projection.v1 nano_usd produced_elsewhere metadata.read + cost.read What would this change have cost on last month's traffic?
budget_standing.v1 nano_usd produced_elsewhere metadata.read + cost.read How much of the budget is left?
attention_rules.v1 count no_producer metadata.read What should somebody be told about without asking?
saved_view.v1 count produced_elsewhere metadata.read What did this person choose to keep looking at?That run went through a loopback server replaying the recorded catalogue; the closed enums follow it on stderr.
A value and its three states
Every measure is one of three things, and only two of them carry a number.
| State | number |
unit |
coverage |
reason |
Meaning |
|---|---|---|---|---|---|
available |
yes | yes | absent | absent | the figure this service stands behind |
partial |
yes | yes | one or more entries | absent | a floor: the truth is larger, for the listed reasons |
unavailable |
absent | absent | absent | one reason | no figure exists to return |
The reason set is closed. Each one implies a different remedy.
| Reason | What it says | What changes it |
|---|---|---|
no_producer |
nothing in this build writes the fact | a later release |
metric_version_absent |
the metric exists; this version of it does not | ask for a version the catalogue publishes |
unpriced_population |
some rows carry no price, so a sum would understate spend | pricing for those rows |
not_observed |
the population cannot produce that observation | nothing: a non-streaming request has no first-output sample |
scope_not_authorized |
the delegation lacked the action this measure needs | a grant, usually cost.read |
content_withheld |
the measure would disclose prompt or completion text | content.read |
coverage_unknown |
the ingest side cannot bound what is missing | retry once the frontier moves |
denominator_zero |
a ratio over an empty population | traffic in the window |
excluded |
every row of the population sits under an exclusion | nothing: the rows were removed |
Numbers travel as decimal strings, including counts. Money is integer nano-USD: one
US dollar is 1000000000, and 1234567890123 is $1,234.567890123. Parsing one into a
binary floating-point number loses digits above about $9,007,199 of spend.
The answer
One answer carries the pin it was computed under and exactly one body: groups for
overview, series and facets; requests or attempts for drilldown; markers for
markers.
| Field | Type | Notes |
|---|---|---|
read_context |
object | the pin; echo it on every continuation of the same view |
minted |
boolean | true when this read created the context |
shape |
string | the shape that was asked for |
groups |
list | one entry per purpose, each with cells of {metric, value} |
requests, attempts, markers |
list | the row bodies; feedback on the feedback grain |
coverage |
list | what the ingest side disclosed for the window |
truncated |
boolean | true when a row bound was reached |
A window with no traffic answers with groups absent. It is not a group of zeros, and a
client that supplies one is publishing traffic that did not occur.
One recorded overview answer, verbatim
{
"read_context": {
"schema": 1,
"central_account_id": "acct_K11111111111",
"workspace_id": "t_K11111111111",
"scope_digest": "f644d618df3314b70c1de5a5cc3d3da82b31dc31af3917ac8af4ab59279e2800",
"authority_epoch": "1",
"catalog_version": 1,
"filter_version": 1,
"projection_generation": "4",
"retention_generation": "1",
"exclusion_generation": "7",
"from": "2026-09-20T12:00:00Z",
"to": "2026-09-21T12:00:00Z",
"display_tz": "UTC",
"purposes": [
"application"
],
"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"
},
"minted": true,
"shape": "overview",
"groups": [
{
"purpose": "application",
"cells": [
{
"metric": "requests.v1",
"value": {
"state": "available",
"number": "1284",
"unit": "count"
}
},
{
"metric": "provider_spend.v1",
"value": {
"state": "unavailable",
"reason": "unpriced_population"
}
},
{
"metric": "cost_per_1000_requests.v1",
"value": {
"state": "unavailable",
"reason": "unpriced_population"
}
},
{
"metric": "tokens.v1",
"value": {
"state": "partial",
"number": "84210773",
"unit": "tokens",
"coverage": [
"ingest_gap:gateway_journal"
]
}
},
{
"metric": "response_latency.v1",
"value": {
"state": "available",
"number": "412",
"unit": "milliseconds"
}
}
]
}
],
"coverage": [
"ingest_gap:gateway_journal"
]
}Every unit in a recorded answer is one of the eight in the closed set above, and it is
the spelling the catalogue publishes for that metric — milliseconds for
response_latency.v1. A recorded unit outside the set fails the lane’s own sweep.
The read context
The pin a read was computed under. Presenting a stale one answers 410 read_context_expired with the remedy refresh_view.
| Field | Notes |
|---|---|
schema, catalog_version, filter_version |
contract versions this answer was computed under |
central_account_id, workspace_id, scope_digest |
who asked, and a digest of the scope that resolved |
authority_epoch |
the epoch of the caller’s authority |
projection_generation, retention_generation, exclusion_generation |
generations of the projection, the retention policy and the exclusion set |
from, to, grain, display_tz, purposes |
the question’s window and framing |
arrival_fence |
the arrival sequence this answer was computed up to |
watermarks |
per producer class: produced_through, indexed_through, last_indexed_at |
coverage, evaluated_at |
disclosed gaps, and when the pin was taken |
A watermark whose produced_through is ahead of its indexed_through means recent
traffic has been written and not yet indexed. The two producer classes are
gateway_journal and control_plane_outbox.
Row shapes
drilldown answers with one of two row kinds. Neither carries prompt text, completion
text or money: the tables behind them hold none.
| Grain | Fields |
|---|---|
request |
logical_request_id, purpose, occurred_at, status, terminal_kind, release_id, final_model, final_provider, attempt_count, total_ms, first_output_ms, first_output_known, streaming |
attempt |
logical_request_id, attempt_id, ordinal, purpose, role, route_changed, output_state, provider_id, provider_model, connection_id, usage_source, usage_known, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, reasoning_tokens, total_tokens, dispatched_at, terminal_at |
feedback |
logical_request_id, application_id, gateway_id, environment_id, issuer_ref, issuer, feedback_id, value, revision, occurred_at, source, match, purpose |
first_output_ms is absent whenever first_output_known is false, which is every
non-streaming request. The same rule governs usage_known on an attempt row: when it is
false, the token fields carry no measurement. Rendering either absence as 0 reports a
figure nobody recorded.
markers answers with marker_kind, release_id, effective_digest, gateway_id,
environment_id, actor_kind and occurred_at.
Saved views
A saved view is a stored question, not a grant and not stored SQL. Opening one re-resolves the reader’s own scope, so two members of a team can open the same view and see different answers.
| Field | Notes |
|---|---|
id, organization_id, workspace_id, owner |
identity and author |
name |
the label a person chose |
audience |
personal or team |
filter_version |
the filter contract the stored query was written against |
query |
an insight-query/v1 document |
time_intent |
fixed, or relative with relative_window_seconds |
display_tz, revision, created_at, updated_at |
rendering zone and row versioning |
customer.saved_views.open answers a document with three members: view, query and
answer. The question and its answer travel together, so a rendered chart and the caption
over it cannot come from two moments of the reader’s access.
Refusals
The catalogue publishes this table, and the clients carry it row for row. A code’s
next_action is the published remedy.
| Code | Status | Next action |
|---|---|---|
forbidden |
403 | refresh_access |
budget_exceeded |
503 | narrow_window |
coverage_unknown |
503 | retry |
snapshot_expired |
410 | start_new_query |
read_context_expired |
410 | refresh_view |
scope_required |
400 | check_selection |
metric_version_unknown |
400 | check_selection |
purpose_mixing_refused |
400 | check_selection |
window_too_large |
400 | check_selection |
invalid_query |
400 | check_selection |
dependency_unavailable |
503 | retry |
validation_failed |
422 | check_selection |
403 and 503 answer different questions. A 403 is a permission the caller does not hold,
and no retry reaches past it. A 503 is this service failing to answer a question the
caller may ask. Both SDKs give them separate classes, InsightForbiddenError and
InsightUnavailableError, and recognition takes the status and the type together: a
403 body claiming coverage_unknown matches neither and arrives as the base problem
error with every field preserved.
The last two rows are the ones a caller meets outside a question of scope or arithmetic:
dependency_unavailable, 503, remedyretry. The analytics hop failed, or the deployment composes no reader. Both SDKs carry it as anInsightUnavailableError.validation_failed, 422, remedycheck_selection. A saved view whose stored question this build can no longer ask refuses onopenand still reads throughget. It is neither a permission problem nor an outage, so both SDKs raise the BASEInsightRefusalErrorfor it rather than either subclass.
Refusal rows arrive from customer.insights.catalog as code, status and
next_action — snake_case, like every other member on this lane. The published table and
each SDK’s own table are equal row for row, in both directions, and the clients’ tests
assert exactly that.
Partial counts and additive spend/token sums are lower bounds. Ratios (including cost per request) and percentiles computed from incomplete populations can move higher or lower as missing rows arrive; they are not lower bounds. A producer with no indexed observation has unmeasured freshness, even if both reported counters are zero.
Open request evidence from a chart
Selecting a supported model, outcome or release in a gateway chart opens Requests with the same UTC window, purpose and environment. Opening the tab starts a fresh read; its arrival fence and coverage are displayed. The current query API returns at most 100 request rows and a truncation flag, without a continuation cursor. Facet and release matching applies to that bounded slice, so its matching row count is not the whole-window count. Narrow the window in Overview when needed. Route and input-size facets are not available in the request projection; the page explicitly refuses to claim those filters matched.
Select a request to read its attempts under the same read context and its separately authorized, current metadata receipt. The receipt shows the observed provider/model, connection, release and route version, protection disclosure and provider cost when available. It does not request prompt or response content. Missing protection evidence does not mean every guard passed. Unpriced, withheld or numerically unsafe costs remain unavailable. An expired read context requires Refresh requests; no automatic retry changes the population silently.
Protection observation counts
protection.v1 counts observed detector evaluations at the unique logical-request,
component and phase grain. Each answer group also carries protection with
observed_requests, unknown_requests, eligible, evaluated, blocked,
modified, errors, not_executed and blocked_requests. All counts use the
same authorized scope, purpose, time, release and read context as that group.
Two evaluated guards on one refused request mean two evaluations and one blocked
request. Eligibility records actual engine selection; configuration alone does
not establish evaluation. A detector that was unavailable or bypassed remains an
explicit error or not-executed observation.
Historical receipts without these observations contribute unknown_requests.
A mixed population reports a partial evaluation count with
protection_observations_missing; an entirely unknown population is unavailable.
An explicitly observed phase selecting no components can report zero evaluations.
Source ingestion gaps remain additional coverage limitations. The TypeScript
SDK exposes InsightGroup.protection; Python and CLI JSON preserve the same
fields. Request drill-down retains logical request identities and its separately
authorized receipt disclosure; counts never grant content access.
Request drill rows may include protection_evaluations: the actual component ID,
immutable component version, phase, eligibility, evaluation and outcome for that
request revision. The component version hashes the selected effective binding
and compiled detector generation; no finding text or request content is included.
An absent observation block is unknown, not a passed protection check. Reopening
uses current read authority and the same source arrival fence as the count.
Completion denominators and spend coverage
Each measured group can include evidence_coverage. Its completion object
states the completed numerator and observed-request denominator, plus mutually
exclusive completed, refused, failed, partial, cancelled and pending-or-unknown
counts. Cancellation takes precedence over other terminal status labels. Source
gaps remain partial coverage; the observed denominator is not an assertion that
unseen requests do not exist. The completion scalar keeps its ratio meaning.
The spend object reports counts only with current cost-read authority:
priced_effects, pending_effects, unpriced_effects, estimated_effects,
estimate_status_unknown_effects and adjustment_effects. These count unique
cost effects, not requests. Estimated and unknown-estimate counts are subsets of
priced effects, so do not add them to the priced count. Signed corrections are
listed separately. The known nano-USD subtotal keeps its existing meaning.
Estimate status comes from the pricing authority’s explicit flag on the first
terminal settlement. Historical effects and correction companions without that
flag remain unknown; absent does not mean false. Pending and unpriced effects do
not acquire a measured zero price. Scope denial withholds the counts; an empty
or unreadable population is unavailable. The TypeScript SDK exposes
InsightGroup.evidence_coverage; Python and CLI JSON preserve these same fields.
Drill into observed latency
A request-grain drilldown query accepts latency_filter with metric set to
response_latency.v1 or first_output_latency.v1. Optional integer min_ms
(inclusive) and max_ms (exclusive) narrow the duration range. Optional outcome
is overall, succeeded, refused, failed, cancelled, partial, or unknown.
These are the same disjoint populations used by the latency summary; cancellation
takes precedence. Missing timing samples and pending requests are excluded,
and first-output timing includes observed streams only. An observed zero remains
zero. Invalid bounds or use on an overview/attempt query are refused.
Keep the same authorized scope, UTC window, purpose, release, and read context
when following a percentile to its requests. Filtering happens before the bounded
request page; truncation remains explicit. The TypeScript and Python clients carry
latency_filter on the normal insight query, and CLI JSON accepts the same query.
A request-grain drilldown query with drilldown: "attempt" accepts
attempt_filter with optional provider_id, provider_model,
cost_effects_only, and logical_request_id members, each up to 512 bytes
and free of control characters. Setting logical_request_id scopes the
attempt page to that one logical request on the server; it is not a
page-local filter applied after the fact. An empty logical_request_id or
use on any other grain or shape is refused as validation_failed.
cost_effects_only requires cost.read and selects from the scoped
cost-effect population at cost occurrence time, naming rows without attempt
metadata unattributed. The CLI accepts
tokentriage cloud insights drilldown --grain attempt --request <id>, and
the TypeScript and Python SDKs carry logical_request_id on
InsightAttemptFilter.
Read feedback rows
A drilldown query with drilldown: "feedback" answers with feedback: one row per
app-reported verdict identity (request, issuer, feedback_id) at its newest revision. The
window selects verdicts by their occurred_at in [from, to), the same population as
app_feedback.v1. Rows are ordered newest occurred_at first, then by request, issuer and
feedback_id, up to page_size; truncated is true when the page is full.
The read needs metadata.read on the scope. The issuer label also needs content.read. A
ttv_ runtime key, including one holding feedback.write, cannot reach this lane and answers
401.
feedback_filter narrows the grain:
| Field | Notes |
|---|---|
logical_request_id |
scopes the page to one request’s verdicts on the server; an empty id is refused rather than read as no filter |
match |
matched, pending or unmatched |
feedback_filter on any other grain or shape answers 400 invalid_query, like an off-grain
attempt_filter. release_ids on the feedback grain is refused the same way: a verdict
carries no release of its own, and a release filter would silently drop every row that has
not joined a request.
Each row carries:
| Field | Notes |
|---|---|
logical_request_id |
the request the verdict names |
application_id, gateway_id, environment_id |
where the verdict was filed: the scope of the key that submitted it |
issuer_ref |
an opaque stored handle, fis_ and 32 lowercase hex characters, on every row; unassigned for a verdict stored before handles existed |
issuer |
the application’s label for whoever rated; present only when the reader holds content.read |
feedback_id |
the application’s question id, verbatim |
value |
up or down |
revision |
the newest stored revision, numbered by the application, as a decimal string |
occurred_at |
that revision’s occurred_at |
source |
always app_reported |
match |
matched, pending or unmatched |
purpose |
the joined request’s purpose, on matched rows only |
matched means the request was found in the reader’s scope. pending means it may not be
indexed yet. unmatched means the gateway journal is indexed past the point the request would
have arrived and no such request was served in the scope the verdict was filed under; while
that progress is unknown, a row stays pending. There is no count of earlier revisions: stored
revisions may already have been folded together, so a count would overstate the trail.
Key rows on issuer_ref. When any row reads unassigned, the answer’s coverage carries
issuer_ref_unassigned once; it qualifies the issuer handles, not a measured population.
The purpose rule: a matched row is filtered by the query’s purposes. A pending or
unmatched row has joined no request, so its purpose is unknown; it is included whatever
purposes the query named, with purpose absent. Count only matched rows toward a purpose.
The CLI reads the grain with tokentriage cloud insights drilldown --grain feedback, taking
--request <id> and --match <state>, and tokentriage cloud request get --feedback reads
one request’s verdicts after its receipt. Both SDKs carry feedback_filter on the insight
query and feedback on the answer, with revision kept as a string.
One recorded feedback read, verbatim
Not yet executed against a deployment.
The question, from the recorded case drilldown_feedback_request:
{
"feedback_filter": {
"logical_request_id": "lrq_K11111111111"
},
"schema": 1,
"shape": "drilldown",
"scope": {
"organization_id": "",
"workspace_id": ""
},
"from": "2026-09-20T12:00:00Z",
"to": "2026-09-21T12:00:00Z",
"display_tz": "UTC",
"purposes": [
"application"
],
"drilldown": "feedback",
"page_size": 50
}The recording’s empty organization_id and workspace_id name nothing, so the lane takes both
from the path. The answer’s data, as a metadata.read reader receives it:
{
"read_context": {
"schema": 1,
"central_account_id": "acct_K11111111111",
"workspace_id": "t_K11111111111",
"scope_digest": "f644d618df3314b70c1de5a5cc3d3da82b31dc31af3917ac8af4ab59279e2800",
"authority_epoch": "1",
"catalog_version": 1,
"filter_version": 1,
"projection_generation": "4",
"retention_generation": "1",
"exclusion_generation": "7",
"from": "2026-09-20T12:00:00Z",
"to": "2026-09-21T12:00:00Z",
"display_tz": "UTC",
"purposes": [
"application"
],
"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"
},
"minted": true,
"shape": "drilldown",
"feedback": [
{
"logical_request_id": "lrq_K11111111111",
"application_id": "app_M11111111111",
"gateway_id": "gw_M11111111111",
"environment_id": "env_K11111111111",
"issuer_ref": "fis_fedcba9876543210fedcba9876543210",
"feedback_id": "fb-7c1e",
"value": "up",
"revision": "1",
"occurred_at": "2026-09-21T11:56:00Z",
"source": "app_reported",
"match": "pending"
},
{
"logical_request_id": "lrq_K11111111111",
"application_id": "app_K11111111111",
"gateway_id": "gw_K11111111111",
"environment_id": "env_K11111111111",
"issuer_ref": "fis_0123456789abcdef0123456789abcdef",
"feedback_id": "fb-2a90",
"value": "down",
"revision": "3",
"occurred_at": "2026-09-21T11:51:00Z",
"source": "app_reported",
"match": "matched",
"purpose": "application"
},
{
"logical_request_id": "lrq_K11111111111",
"application_id": "app_K11111111111",
"gateway_id": "gw_K11111111111",
"environment_id": "env_K11111111111",
"issuer_ref": "unassigned",
"feedback_id": "fb-0001",
"value": "up",
"revision": "1",
"occurred_at": "2026-09-21T11:30:00Z",
"source": "app_reported",
"match": "matched",
"purpose": "application"
}
],
"coverage": [
"issuer_ref_unassigned"
]
}The pending row was filed under another application’s scope and has joined no request there,
so it has no purpose. The recorded case drilldown_feedback_content is the same read by a
reader who also holds content.read: the same rows and handles, each with an issuer label
added.