LLM Gateway Architecture: Core Components Explained
An LLM gateway sits between your application code and one or more model providers, normalizing requests, enforcing auth, and handling the operational concerns you don't want scattered across every service that calls a model. Its architecture typically has five layers: an auth/routing layer, a request normalization layer, a provider adapter layer, an observability layer, and a resilience layer that handles retries, rate limits, and failover.
If you're asking "what does an LLM gateway architecture actually look like," the short answer is: it's a thin, stateless HTTP service (or a small cluster of them) that accepts requests in a consistent format, translates them to whatever the underlying provider expects, streams responses back, and logs everything for billing and debugging. The complexity isn't in any single layer — it's in getting the layers to compose cleanly without adding latency.
The core layers
1. Auth and routing
Every request needs to be authenticated before it touches a model. In practice this means API keys scoped per application or per team, not shared credentials. The gateway validates the key, checks quota/rate limits, and routes the request — either to a fixed provider or, in multi-provider setups, based on rules (model name, cost tier, latency requirements).
Client → [Auth check] → [Rate limit check] → [Router] → Provider adapter
This layer should be fast. Auth checks that hit a database on every request add latency; most gateways cache key validation in memory or Redis with short TTLs.
2. Request normalization
Different providers (or different model families from the same provider) have different request shapes — message formats, system prompt handling, tool-call schemas, streaming event formats. A gateway's normalization layer accepts one consistent input format and maps it to whatever the backend expects. This is also where you validate input before it reaches the provider: message structure, token limits, tool schema correctness.
Normalization matters most when you support multiple providers or multiple API versions. If you're gatewaying a single provider's API, this layer can be thinner, but it's still useful as a place to enforce request shape and catch malformed payloads early.
3. Provider adapters
The adapter layer translates the normalized request into the actual provider call, handles the provider's authentication, and translates the response (including streamed chunks) back into your gateway's output format. Each adapter owns the quirks of its provider: different error codes, different rate-limit headers, different retry-after semantics.
Keeping adapters isolated is what makes it possible to add or swap providers without touching the rest of the stack. A clean adapter interface looks roughly like:
interface ProviderAdapter {
send(request: NormalizedRequest): Promise<NormalizedResponse>;
stream(request: NormalizedRequest): AsyncIterable<NormalizedChunk>;
}
4. Observability
This is the layer teams underbuild early and regret later. At minimum you want, per request: which key/app made it, which model, input/output token counts, latency, and success/failure status. This data feeds three things — cost attribution, debugging, and capacity planning. Without it, "why did our bill spike" and "which endpoint is slow" become guessing games.
Structured logs are usually enough; you don't need a full tracing stack unless you're running multi-hop agent chains where a single user action triggers several model calls.
5. Resilience: retries, rate limits, failover
Providers throttle, time out, and occasionally have outages. A gateway architecture needs a resilience layer that:
- Retries transient errors (5xx, timeouts) with backoff, not on every error type
- Respects and surfaces rate-limit signals instead of hammering a throttled endpoint
- Optionally fails over to a secondary provider or model when the primary is degraded
- Caps concurrent requests per key to protect both your gateway and the upstream provider
This layer is where most of the reliability value of a gateway comes from — it's also the hardest to get right, because retry logic that's too aggressive can turn a minor blip into a self-inflicted outage.
Streaming considerations
Streaming complicates every layer above. The gateway has to hold a connection open, forward chunks as they arrive, and still apply timeouts and token accounting mid-stream. A common mistake is buffering the entire response before returning it — this defeats the purpose of streaming and adds latency for no benefit. Server-Sent Events or chunked HTTP responses should be forwarded as they arrive, with the gateway tracking token counts incrementally rather than waiting for stream completion.
Build vs. use a managed gateway
You can build this yourself — a stateless proxy with Redis for rate limiting, structured logging to your existing pipeline, and one adapter per provider is a reasonable weekend project for a single provider. It gets more involved once you need per-application API keys, team-level usage dashboards, streaming with backpressure handling, and tool-use pass-through.
If you're specifically gatewaying Claude access and don't want to build and maintain this stack, SubToAPI is a managed gateway purpose-built for that case. It turns your existing Claude access into an HTTPS API with application-scoped keys (sub_live_...), streaming, tool use, and usage metadata per key — the layers described above, already wired together. Plans start at €9/month for solo use, with team seats at €19 and €49 for higher-volume/Scale needs, and there's a free trial at signup. See /docs/quickstart for the fastest path to a working key, or /docs/streaming and /docs/tools for the specifics on those layers.
A minimal call against a managed gateway looks like this:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Summarize this architecture."}]
}'
Full request/response shape is in /docs/messages.
Where architecture decisions actually matter
Most of the design decisions in an LLM gateway come down to trade-offs: caching auth checks (speed) vs. instant key revocation (correctness), aggressive retries (reliability) vs. cost control, and thin single-provider adapters (simplicity) vs. a full normalization layer (portability). There's no universally correct answer — it depends on whether you're routing to one provider or several, and whether your traffic is bursty or steady.
FAQ
Does an LLM gateway add noticeable latency? A well-built one adds low single-digit milliseconds for auth and routing — negligible next to model inference time, which is usually hundreds of milliseconds to seconds. Latency problems usually come from buffering streamed responses, not from the gateway logic itself.
Do I need a multi-provider architecture if I only use one model provider? No. A single-provider gateway can skip the normalization layer entirely and just focus on auth, observability, and resilience — that's a simpler and often more reliable design than building for portability you don't need yet.
Should I build my own gateway or use a managed one? Build if gatewaying is core to your product or you need very specific routing logic. Use a managed service like SubToAPI if you just need reliable API access to Claude with per-application keys and usage tracking without maintaining infrastructure — see /pricing for plan details.