AI endpoints are sensitive endpoints. Put auth on them.

July 25, 2026

Look back over this series and you’ll notice a word doing a lot of quiet work: per user. Per-user caps and quotas. Per-user token limits. Per-user attribution. Every one of those controls assumes you can answer a basic question — who is calling? — and that answer is authentication. It’s the base the rest of the security module stands on.

The endpoint teams under-protect

An LLM route is, functionally, one of your most sensitive endpoints: it spends money on every call and it can be abused into someone’s free ChatGPT or a liability. And yet it routinely ships with less authentication than a plain CRUD endpoint — sometimes none at all, just an open path the frontend posts to.

Every control here assumes you know who's calling — that's auth.

The reframe: treat your AI endpoint like a payment endpoint. You wouldn’t expose “charge a card” anonymously to the internet. “Spend tokens” is the same category of action.

What “auth on AI endpoints” means

The common patterns

None of this is novel security — it’s identity, sessions, per-user authorization, and revocation, the things you already do across your app. The only shift is recognizing that the LLM route is an unusually expensive and abusable resource, and deserves at least the auth you give your billing code.

The gate, in code

Concretely, it’s one dependency wrapped around the AI route — it authenticates the caller and hands back who they are before the handler body runs. A FastAPI version:

from fastapi import Depends, FastAPI, HTTPException, Security
from fastapi.security import APIKeyHeader

app = FastAPI()
api_key_header = APIKeyHeader(name="X-API-Key")     # where the caller's key rides

def require_key(key: str = Security(api_key_header)) -> Caller:
    caller = lookup_key(key)                         # your own issued keys, hashed in the DB
    if caller is None or caller.revoked:             # unknown or revoked → no model call
        raise HTTPException(status_code=401, detail="invalid or revoked key")
    return caller                                    # a real identity: id, quota, allowed models

@app.post("/v1/chat")
def chat(body: ChatRequest, caller: Caller = Depends(require_key)):
    # auth ran FIRST — nothing below executes for anonymous traffic
    enforce_quota(caller)                            # per-identity cap/breaker keys off caller.id
    return run_llm(body, owner=caller.id)            # attribution rides the authenticated id

FastAPI resolves Depends(require_key) before it ever enters the handler, so an unauthenticated request 401s at the door and never reaches the model call. The dependency doesn’t just say yes or no — it returns the identity, and that same caller.id is what the quota check and the per-run breaker key off, and what tags the run for attribution. Swap the header lookup for JWT verification and the shape is identical: authenticate, resolve an identity, attach it, then spend tokens.

One honest boundary, to keep the layers straight: auth stops anonymous abuse and unlocks every per-user control. It does not stop a legitimate account that’s been compromised (that’s quotas plus anomaly detection) or prompt injection (that’s Days 7–8). Auth is the foundation, not the whole house.

Next in the series — Day 15: a fail-closed AI gateway you can copy — where every control from the last two weeks composes into one door. (Day 14’s kill switch already got its own post, so we go straight to assembly.)


If your AI endpoint is reachable without a real, revocable identity behind it, none of the per-user controls you think you have are actually enforceable — and wiring that up is part of what I do. Every message comes straight to me — I read and reply to each one myself, usually within a day, and what readers send shapes what I build next. It’s just me for now, so that’s genuinely true; it won’t be forever. Send me your setup and I’ll tell you what’s calling your model unauthenticated — free, within a business day.

Free live workshop: cap your AI spend (Oct 8)

A hands-on 90-minute session — wire a fail-closed spend cap + cost-aware rate limiting against a real stack, live, so a leaked key or runaway agent can't run up your bill. Full details & times → Save your seat (and get each new lesson as it lands):

Double opt-in — one email to confirm. The lessons are free; the course is optional. No spam, unsubscribe anytime.