Skip to content
SeeItFlow
System Design · 11 chapters · ~30 min read

AI Gateway System Design: From Proxy to Production

An AI Gateway looks like an HTTP reverse proxy at first glance — many callers, one or more backends. It isn't. What it actually protects is identity, money, and the semantic correctness of a streamed, non-deterministic response. This module builds one step by step: direct SDK calls, a noisy-neighbor incident, virtual keys and quotas, model-aware routing, streaming failure semantics, budget reservation, and a real distributed control/data-plane platform.

Mangesh MandlikLast updated: How lessons are made
Chapter 01

Problem Definition

Acme Support needs an LLM answer today — one team, one provider, one API key. Eleven other teams inside the same company are about to want the exact same thing. The question is how you give all twelve teams access to one or more model providers without any team's traffic, budget, or safety posture bleeding into another's.

This is not primarily a networking problem, even though it looks like one. A generic reverse proxy already solves 'route requests from many callers to a backend.' What it doesn't solve is: who is calling, what are they allowed to use, how much can they spend before anyone notices, what happens when a response is half-streamed and the provider disconnects, and how do you add a second provider without rewriting every client.

Swipe to pan
Three Responsibilities, Not Forty ComponentsEvery component in this design exists to serve exactly one of theseGovern Accessidentity · ACL · atomic admission · budget reservationRoute Reliable Inferencecapability filter · health · residency · circuit breakingAccount For Usageidempotent ledger · reconciliation · metadata-only tracing
Three independent responsibilities — every component in the final design traces back to exactly one of these

Strip away implementation detail and an AI Gateway has three jobs: govern access (identity, model permissions, atomic admission against shared budgets), route reliable inference (pick a healthy, compatible, compliant deployment and translate honestly to its native shape), and account for usage (reserve before committing, settle to the real number afterward, trace without needing the prompt text). Nearly every design mistake in this space comes from solving one of these three and assuming the others come for free.

Interactive

Visual Design Journey

Watch an AI Gateway evolve from a single direct SDK call into a real multi-tenant platform — virtual keys, atomic token/budget reservation, model-aware routing, circuit breakers, streaming failure semantics, and a control/data-plane split — each piece earned by a concrete incident, not added by assumption.

Chapter 02

Requirements

Scope this the way you would in an interview — explicitly, before any diagram — because the scope decisions themselves are half the design.

Functional (V1)

Chat and text-generation completions, including server-sent-event streaming. Backend/service clients only — no browser ever holds a provider credential. Tenant and model access control from day one. Two upstream provider deployments to start, reachable through one or more logical model names. Usage metering and a spend budget per tenant. Embeddings, image generation, and autonomous multi-step agents are explicitly out of scope for V1 — they have different latency, payload, and safety shapes and deserve their own design.

Non-functional

Tenant isolation

One tenant's traffic, quota exhaustion, or misconfiguration must never degrade another tenant's service. This is the #1 correctness invariant for this design — the equivalent of 'no double-selling' in a booking system.

Bounded financial exposure

Because output length isn't known until generation finishes, the gateway can never promise zero possible overshoot against a budget — only a documented, bounded worst case, enforced by an atomic reservation made before admission.

Streaming honesty

Once a response has started streaming to a client, a retry or fallback can never silently replace what's already been shown with content from a different generation. Partial failure must be surfaced explicitly, never papered over.

Provider-format honesty

A shared request/response shape across providers is a convenience, not a guarantee that every provider supports every feature. An unsupported capability is rejected explicitly, never silently dropped.

Low admission overhead

Authentication, authorization, and quota/budget reservation sit on every single request's critical path — they need to add single-digit milliseconds of overhead, not a noticeable fraction of total latency, which is comfortably true for an atomic cache operation and comfortably false for a synchronous call into a slow control plane.

Prompt privacy by default

Operational observability (tenant, model, tokens, cost, latency, routing decision, finish reason) must be fully traceable without ever requiring the prompt or completion text. Payload capture is opt-in, purpose-limited, and redacted.

Illustrative scale

For reasoning purposes only: 120 req/s average, 1,200 req/s peak for ~5 minutes, ~1,500 average input tokens, up to 500 requested output tokens, ~10% of requests are long streams. These are planning numbers, not measured production traffic — treat every number in this module the same way.

Chapter 03

V0 — Direct SDK Integration

Start with the simplest thing that could possibly work: Acme Support's backend holds a provider API key directly and calls the provider's chat completion endpoint.

This is not a strawman. For exactly one team calling exactly one provider, with the key held server-side and never shipped to a browser, V0 is a completely legitimate production architecture. A gateway earns its complexity later, by solving demonstrated problems — not by assumption.

The cracks appear the moment a second team needs the same capability. Each new team writes its own HTTP client, its own retry and timeout logic, its own parsing for that provider's specific error shapes. One team handles 429s gracefully; another silently returns a blank response to its users. Nobody can answer 'how much are we spending on this provider today' without a shared place to look. And twelve independently-held API keys scattered across twelve codebases is a security and operations problem on its own — rotating one key means coordinating twelve deploys.

Chapter 04

The Reverse Proxy and the Noisy Neighbor

Put one stateless HTTP proxy in the middle. Every team calls one URL instead of calling the provider directly. Integration duplication mostly disappears — but a new failure mode appears almost immediately.

Swipe to pan
Noisy NeighborA tenant-blind proxy forwards whatever arrives through a shared credentialTeam B (batch)Team A (3 reqs)Tenant-blind ProxyProvider (RPM exhausted)💥 Team A's requests all 429 — no fault of their own
A tenant-blind proxy forwards whatever arrives through a shared provider credential

A generic reverse proxy (API Gateway fundamentals covers this layer in general) solves transport-level duplication: one place for retries, one place to see traffic. What it does not solve is that the proxy has no concept of who is calling. It forwards every request with the same shared provider credential, to the same upstream, regardless of source.

Nine in the morning: Team B kicks off an overnight reconciliation batch — thousands of requests, through the shared proxy, to the shared provider deployment. The provider's own rate limit is shared across every caller using that credential, so Team B's batch alone exhausts it. Team A sends three ordinary requests a few minutes later. All three come back 429 — not because Team A did anything wrong, but because somebody else used up the shared budget first.

Chapter 05

Identity: Virtual Keys & Model ACLs

Fix the proxy's blind spot by giving every caller its own identity. This single change is what makes every governance decision after it possible.

Virtual keys

Each tenant gets a key the gateway itself issues and controls — not the provider's real key. The gateway maps that virtual key to a tenant, a team, and a policy. The real provider credential is injected server-side, immediately before the call upstream, and never returned to a client or logged in plaintext.

Model access control lists (ACLs)

Identity alone isn't enough — the gateway also needs to know what each tenant may use. Team A's policy might allow a fast, cheap model class but not an expensive reasoning model. A disallowed model request is rejected with 403 *before* any network call to a provider — no tokens spent, no upstream latency, no leaked capacity.

401 vs. 403 — a real distinction

A request with no valid credential at all gets 401 (unauthenticated — 'I don't know who you are'). A request with a valid credential naming a disallowed model gets 403 (unauthorized — 'I know exactly who you are, and the answer is no'). A well-built client treats these very differently: 401 might mean 'refresh my credential,' 403 should never be blindly retried.

Chapter 06

Atomic Admission: Quota & Budget Reservation

Identity tells the gateway who is asking. It doesn't yet protect a tenant's fair share of a shared resource across multiple gateway replicas — that needs atomic admission.

Swipe to pan
Request Lifecycle (Happy Path)authenticate → authorize → reserve → route → adapt → stream → finalizeClientAuth(vkey)ACLReserve(quota)RouteAdapterProviderFinalize(ledger)
Admission is one stage in the full request lifecycle — authenticate, authorize, reserve, route, adapt, stream, finalize

Run two stateless gateway replicas behind a load balancer, each checking its own local, in-memory counter for 'tokens remaining this minute.' Say one unit of budget is left. Both replicas see '1 remaining' locally. Both admit a request. The real shared limit is exceeded — not because either replica made a mistake, but because *local* and *shared* are different things, and nothing coordinated between them. This is the same read-then-write race as any double-booking bug.

Shared atomic counter

Replace local counters with a shared, atomic store (a Redis-backed token bucket is the common choice) that every replica calls before admitting a request. The check-and-decrement happens as one indivisible operation, so only one of two simultaneous requests for the last unit can win; the other is denied with honest retry guidance (the provider's real Retry-After, not an invented number).

RPM and TPM are separate constraints

A request-count budget and a token budget don't move together — one large request can exhaust token budget while barely touching request-count budget. Both need their own atomic reservation, checked independently.

Reserve the worst case, settle the real number

At admission time, output length is unknown — only the client's requested cap is known. Reserve pessimistically: estimated input tokens (using the right tokenizer for this model) plus the full requested output cap. When the request finishes using less, the unused part is refunded to the budget at settlement. The number shown mid-request is a reservation, not a bill.

Budget reservation is the same pattern, for money

The identical race exists for spend: forty concurrent requests, each individually fine against a daily cap, can collectively blow past it if spend is only checked *after* each request completes. The fix is the same shape — reserve a conservative dollar estimate atomically at admission, record the real amount in the ledger on completion and refund the rest of the reservation. This bounds exposure; it does not make overshoot impossible, because tokenizer differences and delayed provider usage reporting leave a small, honestly-documented gap.

Chapter 07

Model-Aware Routing

A client naming a provider's exact model directly is fragile — deprecate that model, or add a second provider for the same job, and every caller's code has to change. Introduce a logical model name instead.

Logical models

Clients only ever ask for a stable name like 'support-fast.' The gateway's model registry maps that name to one or more approved deployments, each with documented capabilities: streaming, tool calls, context length, JSON mode. This is a stable contract for the client and a flexible routing decision for the gateway.

Adapters translate honestly

Each provider deployment has its own adapter that translates the gateway's standardized request into that provider's native shape. When a target deployment can't honor a requested capability — say, structured JSON output — the adapter rejects the request explicitly rather than silently dropping the requirement. A caller that thinks it got structured output but actually got free text can crash downstream, or worse, silently misparse.

Filter first, score second

The router eliminates any candidate that fails a hard constraint — required capability, data residency, policy — *before* comparing price or latency. A cheaper, disqualified deployment never enters scoring. Getting this ordering backwards is how compliance incidents happen: scoring first and discovering the disqualification afterward.

Bounded fallback before commitment

If the first-choice provider rejects a request (429, 5xx) *before any bytes have been sent to the client*, a bounded retry against a capability-compatible, healthy alternative is safe. The client never sees the first provider's bad moment. This safety condition disappears the instant a response starts streaming — see Chapter 08.

Circuit breakers stop hammering a failing deployment

Six consecutive failures from a deployment opens its circuit — new requests skip it entirely for a cooldown window, favoring a healthy alternative. After cooldown, a single half-open probe decides whether to close the breaker (deployment is trusted again) or restart the cooldown. See Circuit Breaker for the general pattern this specializes.

Chapter 08

Streaming Changes the Rules

Most of this traffic streams. Streaming is not a transport detail bolted onto the same failure model as a normal request — it changes what failure and retry even mean.

Swipe to pan
Streaming Failure SemanticsPre-first-byte failures can fail over silently. Post-first-byte failures cannot.CONNECTINGSTREAMINGchunks flowingCOMPLETEusage finalizedFAILED MID-STREAMexplicit error, marked incompleteCLIENT CANCELLEDreconcile, don't assume $0Once output is committed, a retry cannot splice in a different generation (Invariant I4)
Pre-first-byte failures can fail over silently. Post-first-byte failures cannot.

TTFT vs. total completion time

Time-to-first-token and total completion time fail independently — a system can have excellent TTFT and still take ten seconds to finish. Track both; a single flat latency number stops meaning much once ~10% of requests are long streams.

Backpressure on slow readers

If a client reads slowly (a flaky connection, a backgrounded tab) and the gateway keeps buffering provider chunks without limit, enough concurrent slow clients becomes an unbounded memory problem. Apply the same flow control any producer-faster-than-consumer system needs — see Backpressure — slow reading from upstream, or cap the buffer and drop the connection past a limit.

Mid-stream failure is not retryable the same way

Seventeen chunks into a response, the provider connection drops. The client has already rendered half an answer — there's no safe way to seamlessly continue that exact sentence on a different model or a different generation; claiming it's the same completion would be a lie. The gateway terminates the stream with an explicit error event and marks the response incomplete. It never pretends success.

Client cancellation isn't automatically a refund

If a caller disconnects mid-stream, the gateway propagates the cancellation upstream where supported and frees the concurrency slot immediately — but the provider may already have generated (and billed) tokens the client never received. The reservation stays charged until the provider's usage report confirms what was actually generated, and only then is the unused part refunded.

Chapter 09

Spending, Safety & Observability

Three more properties earn their place before this is a real platform: idempotency under retry, content safety that doesn't substitute for authorization, and tracing that never needs the prompt.

Idempotency has a real limit

A client that times out and retries with the same idempotency key gets its ledger entry deduplicated to one — but the original request may still be executing upstream when the retry goes out, so the *provider* can end up running inference twice for what the client experiences as one logical request. A local ledger (Idempotency) dedupes your own bookkeeping; it cannot force a remote provider to have exactly-once execution semantics. State that gap honestly rather than promising it away.

Guardrails filter content; they don't replace authorization

A guardrail that detects and redacts a pasted secret before it reaches a provider or a log line is useful, and imperfect — false positives and false negatives are both normal. But a prompt that tries to talk its way into an disallowed tool call is an authorization problem, not a content-filtering one. No amount of pattern matching on text substitutes for the tenant's actual model/tool permissions enforced back in Chapter 05.

Observability without payloads

A single correlation ID follows a request through every hop — client, gateway, chosen deployment, back — carrying tenant, logical model, deployment chosen and why, TTFT, estimated/actual tokens, estimated/actual cost, any policy action taken, and the finish reason. None of that requires the prompt body; payload capture stays off by default.

Chapter 10

Scaling to a Real Platform

Twelve hundred requests a second, with long-running streams in the mix, needs more than one gateway process — and needs policy to change without touching the traffic path.

Swipe to pan
Control Plane / Data Plane SeparationPolicy changes rarely; traffic arrives constantly. Different latency budgets, different systems.Gateway gw1Gateway gw2Gateway gw3Control Planepolicy · pricing · credentialsversioned snapshot push (v42)Never a live admin-DB query inside a per-request hot path
Control plane (policy, pricing, credentials) pushes versioned snapshots to a stateless data plane — never a synchronous lookup per request

Stateless data-plane replicas

Spread traffic across multiple gateway replicas behind a load balancer; all of them share the same atomic quota store and the same durable ledger, which is what makes 'stateless' safe — any replica can handle any request because none hold state the others lack. Replicas scale request-handling throughput and failure isolation; they do not increase the upstream provider's own rate limit, which remains the real ceiling.

Control plane / data plane separation

Tenant policy, the model registry, pricing, and provider credentials change far less often than requests arrive, so they live in their own control plane. Changes produce a new versioned policy snapshot, pushed asynchronously to every gateway replica — never a live admin-database query inside a per-request hot path, which would put the slowest, flakiest part of the infrastructure on the critical path of every chat response.

Control-plane outages are survivable, with an honest caveat

Because gateways hold the last-known-good snapshot, they keep serving normally during a control-plane outage. The documented cost: a permission revoked moments before the outage might not take effect until the next snapshot arrives, bounded by the policy's freshness window. A design that insists on instant revocation would need a synchronous control-plane call on every request — exactly the hot-path dependency just avoided.

Compliance beats convenient failover

If an EU-restricted tenant's required deployment goes down, a healthy US deployment of the same logical model is *not* an eligible fallback — compliance constraints filter the candidate set before the router ever scores anything, exactly as in normal-path routing, just now under failure.

Chapter 11

Final Architecture

Every component below exists because of a specific, demonstrated failure earlier in this story. None of it is decoration — and a small, single-provider deployment may not need every piece at once.

Swipe to pan
AI Gateway, Final ArchitectureData plane (solid, synchronous) + control plane (dashed, asynchronous)ClientsGatewaystateless ×NGatewaystateless ×NRouterAdapter AAdapter BProvider Aus-east-1Provider Beu-west-1Quota StoreLedgerControl PlaneTelemetry
Data plane (solid, synchronous) and control plane (dashed, asynchronous) — the complete AI Gateway
Key takeaways
  1. 01Gateway (stateless ×N): authenticates virtual keys, enforces model ACLs, orchestrates admission and finalization
  2. 02Quota Store: shared, atomic RPM/TPM reservation — the admission hot-path coordination point
  3. 03Router: capability-aware filtering (residency, capability, policy) then scoring (price, latency, health)
  4. 04Model Registry: logical-model → approved-deployment mapping with documented capabilities
  5. 05Adapters: per-provider request/response translation with explicit capability-mismatch rejection
  6. 06Providers: upstream model deployments, tracked individually for health and circuit-breaker state
  7. 07Ledger: durable, idempotent usage/budget settlement — authoritative, not the hot-path check
  8. 08Control Plane: policy, pricing, and credential distribution via versioned async snapshots
  9. 09Telemetry: correlation-ID tracing across every hop, metadata-only by default