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

Gateway insight reads

The insight operations, the metrics and units they answer in, the states a value can carry, and every refusal code.

ReferenceLast updated

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, remedy retry. The analytics hop failed, or the deployment composes no reader. Both SDKs carry it as an InsightUnavailableError.
  • validation_failed, 422, remedy check_selection. A saved view whose stored question this build can no longer ask refuses on open and still reads through get. It is neither a permission problem nor an outage, so both SDKs raise the BASE InsightRefusalError for 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.