Skip to main content

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, 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