Skip to content

AI Gateway

One endpoint for every model

A self-hosted LLM gateway in front of every AI workload. Services hold a virtual key with an allowlist, not a provider key, and spend lands against the tenant that caused it.

2026
every AI workload, one endpoint
phosphor P3 · amber
// try it

Two different ways to get a 401

Pick a consumer and a model. A model can be fully deployed and still refused, because existing and being permitted are separate facts. That catches people constantly, including me.

consumer key
requested model
authenticate virtual key
model in gateway config
model on this key's allowlist
stamp attribution
route to provider
write spend log
before: no gateway

The third API key is the one that hurts

Our first AI feature shipped with a provider key in a secret and nobody minded. By the third I could see where it was heading: keys scattering across repos, and nobody able to say what any of it cost.

None of that is an AI problem. It's unmanaged database credentials wearing a hat, and it has the same fix. I built this while we had two consumers, which is the only reason it was a small job.

request · auth + allowlist

Virtual keys, and a model list you can't talk your way past

Services don't hold provider credentials any more. They hold a virtual key with an explicit list of models it may call. Ask for anything else and you get a 401.

A gateway that helpfully substitutes another model gives you something that works and behaves differently from the thing you tested. I'd rather it broke.

onboarding a model, both halves required
# 1. the gateway has to know the model exists
#    (model_list entry in the gateway config, shipped via GitOps)

# 2. every key that should reach it has to be told, separately
curl -X POST "$GATEWAY/key/update" \
  -H "Authorization: Bearer $MASTER_KEY" \
  -d '{"key": "sk-…", "models": ["gemini-2.5-flash", "gemini-3.6-flash"]}'

# skip step 2 and consumers get 401 for a model that
# demonstrably exists. what can this key actually reach?
curl -H "Authorization: Bearer $VIRTUAL_KEY" "$GATEWAY/v1/models"

Those two halves being separate is the sharp edge, and it's caught me more than once. A model can be fully deployed and still 401 for everybody, because existing and being permitted are different facts. It's written down now.

request · spend tags

Whose spend is it?

Every call carries its tenant, plus tags for environment and feature. Chat, scheduled estate summaries and the nightly schema compile all bill to one service but answer completely different questions about cost.

So "what is the AI costing us" became answerable per tenant and per feature, which matters the first time somebody asks about cost to serve.

postmortem· the 4x pricing bug · severity: embarrassing
symptom
Spend read about fourfold high. For weeks.
cause
The cost dashboard multiplies tokens by per-million prices held as dashboard variables, and ours were set to a different model's pricing than the one deployed.
why it lived
Nobody questioned it, because the number was in a dashboard and dashboards look authoritative.
lesson
Tokens are measured. Prices are config, and config rots.
after: what got cheap

What it made cheap

The point of a seam is what gets easy afterwards. There's an automated pull request review agent running on PRs across eight production services now, and building it needed no provider credentials, no billing conversation, no new secret.

Model upgrades became config too. Moving a consumer between versions is an allowlist entry and an env var, which is how the estate followed three generations of the same model family without anyone rewriting a consumer.

review: the calls

Why it's shaped like this

Fail closed, and loudly

An unpermitted model is a 401, never a silent substitution. A gateway that picks something else for you makes cost and behaviour unpredictable at the same time, and you find out from the bill.

One gateway, not one per environment

It started as a deployment per environment and consolidated down to a single instance with environment as a tag. Fewer moving parts, and spend I can compare across environments instead of summing across dashboards.

Buy the boring middle

This is LiteLLM doing what it says on the tin, on Kubernetes, managed through GitOps like everything else. The engineering worth doing was the key and allowlist model, the attribution scheme, and the runbook. Nobody needed me to write a proxy.

200 OK · where it stands

Where it stands

1

endpoint for every AI workload

Per-key

model allowlists, fail closed

3

tags: tenant, environment, feature

~4x

spend overstatement the pricing bug hid

It's an unglamorous piece of infrastructure and that's roughly the recommendation. Built before the sprawl rather than after it, which is the only reason adding an AI feature here is now a config change instead of a procurement conversation.

Thanks for reading.

If the third API key is already hurting, it's a config change away from not hurting. Happy to talk it through.