> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Request-metrics thin thresholds: windows, gates, and aggregate predicates

> Which bounds and thresholds decide what the API request-metrics pipeline records and what the analytics aggregates actually measure — the window filter chain, the feature-flag gate, the 429/rejection exclusion in latency percentiles, and how an excluded class drifts the results band.

# Request-metrics thin thresholds: windows, gates, and aggregate predicates

The Developer API analytics and request-logs reads look like simple query
endpoints, but four bounded filters run between your request and the
aggregate it returns. This page names each one so you can predict the
numbers: the window filter chain that decides the time range, the
feature-flag gate on the advanced block, the rejection exclusion the
latency aggregates apply, and the pipeline bounds that thin which rows
ever get recorded. It complements the
[request-metrics pipeline concept](/concepts/request-metrics-and-analytics-pipeline),
which owns the write path — this page is about the thresholds that decide
what lands and what the reader drops.

## 1. The window filter chain: start, end, trailing bound

Every time-bounded read resolves its window through the same precedence
order. The `/stats/request-logs` viewer (the Developer → Request Logs
surface) applies it verbatim; the advanced message-analytics block under
`/stats/analytics` accepts the same three inputs:

1. **`start_date` + `end_date` (custom range)** — a `YYYY-MM-DD` pair. The
   start is inclusive day-start, the end inclusive day-end, and the pair
   takes precedence over every trailing form below.
2. **`days` (trailing bound)** — the dashboard's preset ranges resolve to
   this; capped at 365 days.
3. **Bucket preset** — request-logs also accepts a fixed set
   (`1h`, `6h`, `24h`, `7d`, `30d`, `90d`).
4. **Default** — with no window input at all, request-logs reads the
   trailing 24 hours and the analytics block reads 30 days.

A value that fails shape validation is dropped silently (it falls through
to the next precedence level) — you get the default or a wider window, not
a 400. Window bounds are tenant-side controls over which range the query
scans and are identical for every reader you ask them of.

## 2. Origin-protection gate: the advanced-analytics feature flag

The `/stats/analytics` block (summary, channels, volume, delivery funnel)
sits behind a per-tenant feature flag. When the flag is off for your
workspace, the block returns `403 FEATURE_DISABLED` — a deliberate
origin-protection response that keeps the heavy aggregate reads out of
reach of tenants that have not opted in. When the flag is on, the same
window chain above runs and returns the full payload with a 200.

This gate is the only feature-flagged read on the stats surface. The
always-on widgets (summary, channel-health, usage, activity, request-logs,
dashboard) are available to every reader with the `analytics:read` scope
regardless of the flag; your client fields either the full payload or the
403, and it should treat the 403 as "not enabled", never as an error to
retry.

## 3. Predicate thresholds on the latency aggregates

Thresholds inside the API-analytics aggregates shape what the p50/p95 and
mean latency actually measure rather than just which rows exist. The head
aggregate excludes rate-limit rejections (`429`) from the latency
percentiles and the mean: a rejected request is answered at the
rate-limit guard in a few milliseconds before the handler runs, and
folding those rejections into the percentiles would pull the rendered p50
far below your real handler latency. Excluding them keeps the quality
numbers meaningful while the volume counters still count every metered
row — 429s included.

The same predicate discipline runs on the write side: the pipeline names
its self-observation and keepalive paths on an explicit skip list (the
health probes, the API-analytics console's own reads, presence
heartbeats, and realtime transports) rather than drawing a blanket
"everything authenticated counts" line. A row that matches the skip list
never enters the aggregate, so the percentiles measure genuine API
traffic instead of the tab that rendered the dashboard.

## 4. When an excluded class dominates, the band coarsens

Exclusion thresholds guard the quality metrics, but they also mean the
rendered percentile drifts when the excluded family dominates the window.
Two concrete shapes to recognise:

* **A 429 spike lowers the aggregate numerator, not the latency mean.** A
  tenant being rate-limited sees its error-rate card climb while the
  latency percentile stays pinned to the served subset — a healthy shape,
  not a contradiction.
* **A skipped transport row no longer counts.** The keepalive and
  self-observation skips mean the "API Requests (24h)" counter reads
  operator-intent API calls only; a quiet dashboard tab with a pinned SSE
  connection contributes no API-call rows at all, so a one-day idle tab
  stops inflating the count the way it used to.

If the latency number still reads far below your expectation after the
thresholds, check for the residual historical rows: the read-side filter
continues to exclude skip-list rows metered before the skip shipped for
up to a 30-day window, but a pre-skip row can still sit in a longer
window for a few days.

## 5. Idempotency spot-check: one row per request, one namespace per tenant

Two invariants keep the pipeline safe to re-consume:

* **Request id is the row key.** Every completed request writes exactly
  one telemetry row, attributed to your tenant, so a reader that pages or
  re-queries the same window sees the same set — the row already exists
  or it does not, and no read ever mutates it.
* **Tenant id is the only namespace.** A row with no resolvable tenant
  context is dropped rather than keyed under a wrong or empty organ, so
  the aggregate never mixes another workspace's traffic into yours — and
  your own traffic never lands in theirs.

The write path is deliberately lossy in the safe direction: it buffers
rows into Redis with a capped queue and drops (and counts) rows on a
queue-cap or a Redis fault rather than ever delaying your response. A
degraded pipeline means a thinner dataset, never a stalled API — and the
dropped rows are surfaced in the enqueued/dropped counters, so the loss
rate stays observable instead of silently shifting your percentiles.

## 6. Wiring it into the console

Read the head aggregate (`/developers/api-analytics/overview`) for the
threshold-cleaned p50/p95, the per-endpoint breakdown for the per-route
predicate result, and `/stats/request-logs` for the per-request drill into
a filtered window — the same window chain you set with `start_date` /
`end_date` / `days`. When you write a client of these reads, follow the
chain order in section 1 so a re-invoked preset (for example `days=30`)
takes precedence exactly the way the dashboard's own picker passes it, and
handle the section-2 403 as a feature-flag state rather than an exception.

## See also

* [Request-metrics pipeline concept](/concepts/request-metrics-and-analytics-pipeline) —
  the write-side hook, the Redis buffer, and the read surface this page
  thresholds.
* [Request Logs model](/concepts/request-logs-model) — the sibling Cloud
  Logging read this page's window chain drives.
* [The stats surface map](/concepts/observability-stats-surface) — which
  route family answers which question.
