Skip to main content

API request guard pipeline

Every API request to Orbit walks an ordered chain of per-request guards before it reaches a route handler. One request can fail for many reasons — a denylisted IP, a saturated event loop, a spent rate-limit bucket, a duplicate idempotency key — and the guards are sequenced so the cheapest, most defensive checks reject first. When you know which guard fired and in what order, a 403/429/503 stops being a support ticket and becomes a reading exercise on response headers and the error envelope. This page maps the chain, the signal each guard returns, and how to read the order in the code. For per-endpoint limit tables see Rate limits; for the full limiter taxonomy see Rate-limit and cooldown taxonomy.

Why a chain matters

Guard ordering fixes three questions that otherwise arrive as support tickets:
  1. Which guard fired? A 403 from the IP denylist is a block; a 403 from your org-level allowlist is a misconfiguration. The order tells you which surface rejected the request before auth ever ran.
  2. What got skipped? A rejected request never reaches later guards — a denylist 403 never consumed rate-limit quota, and a 429 shows the idempotency guard was skipped on that attempt.
  3. Where is the fix? Each guard names a distinct owner. Denylist and allowlist entries are operator-curated; rate-limit buckets are your plan’s per-endpoint tables; the under-pressure shed is platform-side and retryable.
The chain is deliberately layered in fastest-reject-first order. A brute-force source is turned away by the IP denylist before it ever reaches the auth code path it was hammering.

The ordered guard chain

The table below lists every guard in registration order. Guards marked terminal end the request when they fire; the rest annotate the request for downstream consumers. Two consequences fall out of the ordering:
  • 429 before 401 means auth effects are never wasted. The rate limiter runs at preHandler, keying on the API-key header or client IP, before auth populates the request context — so a throttled client never consumes auth/tenant work it would have failed anyway.
  • The denylist rejects cheaper than rate-limit. A platform-denied IP is rejected in guard 3, before any Redis-side bucket decrement — which is why a denylisted source can hammer the API forever without ever spending quota.

Per-guard triggers, status, and response envelope

Every terminal guard returns the error envelope the error-handler registers last — an error object plus a meta block carrying request_id and docs_url. You can correlate a guard’s request_id against your own logs even when the guard fired before any handler ran.
The same 403 status fires from both the platform denylist and the per-org allowlist. If you get a 403 with no allowlist configured on the org, treat it as the platform denylist and contact support; if the org has an allowlist, fix the list first.

Where to read the order in code

The chain is registered in one block — the biggest part of the sequence sits in apps/api/src/app.ts between the first clientIpSocketPlugin registration and the final errorHandler registration, with the request-annotation plugins (request id, trace context, Sentry, metrics, locale, logger, metrics, idempotency, policy scan, error handler) in one contiguous block near the end of buildApp. The plugin implementations live one-per-file in apps/api/src/plugins/, named after the guard they run (retired-host-guard.ts, platform-ip-denylist-guard.ts, under-pressure.ts, etag.ts, request-id.ts, trace-context.ts, locale.ts, request-logger.ts, request-metrics.ts, idempotency.ts, policy-scan-hook.ts, error-handler.ts). Reading that block top-to-bottom is the definitive order; this page is the map, the file is the territory.

Who owns each guard

All the envelope and ordering above is platform-owned plumbing — not tenant compliance controls. The tenant-owned controls sit behind auth (the org-level IP allowlist, per-endpoint config.rateLimit buckets on routes that opt out of the global limiter, idempotency-key semantics you choose). See Tenant isolation for the tenancy split and Compliance for the tenant-ownership model.

See also