~/TechPurAI
~/tutorials/ai-projects-with-python/adding-authentication-and-rate-limiting-to-your-ai-api
intermediate·part 7 of 22·3 min read

Adding Authentication and Rate Limiting to Your AI API

Updated Aug 16, 2026Python · AI

Part 6's /qualify-lead endpoint has no real access control at all — anyone who finds the URL can call it, generating real, billable API costs. This part adds two genuine production requirements: authentication and rate limiting.

Real, practical API key authentication

python
# auth.py
from fastapi import Header, HTTPException
import os

VALID_API_KEYS = set(os.environ.get("VALID_CLIENT_KEYS", "").split(","))

def verify_api_key(x_api_key: str = Header(...)):
    if x_api_key not in VALID_API_KEYS:
        raise HTTPException(status_code=401, detail="Invalid API key")
    return x_api_key
python
# main.py
from fastapi import Depends
from auth import verify_api_key

@app.post("/qualify-lead", response_model=LeadQualificationResponse)
def qualify_lead(request: LeadQualificationRequest, api_key: str = Depends(verify_api_key)):
    # ...unchanged from part 6

Depends(verify_api_key) is FastAPI's real, built-in dependency injection mechanism — every real request to this endpoint now runs verify_api_key first, rejecting an unauthenticated request with a genuine 401 before it ever reaches the actual AI logic or costs a single real API token.

Why it matters

This directly matters because of exactly what part 6 already flagged — every real call to this endpoint costs actual, per-token money (per the AI Fundamentals series' own token-cost coverage). An unauthenticated AI endpoint isn't just an access-control gap the way an unauthenticated blog-comment endpoint might be — it's a genuine, direct, ongoing financial exposure to anyone who discovers the URL.

Real, practical rate limiting

bash
pip install slowapi
python
# main.py
from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter

@app.post("/qualify-lead", response_model=LeadQualificationResponse)
@limiter.limit("10/minute")
def qualify_lead(request: LeadQualificationRequest, api_key: str = Depends(verify_api_key)):
    # ...unchanged

@limiter.limit("10/minute") caps real requests per client to a genuine, sane rate — directly protecting against both a real, accidental infinite-loop bug in a calling application and a genuine, deliberate abuse attempt, either of which could otherwise generate unbounded real API cost.

Rate limiting per API key, not just per IP address

python
def key_func(request):
    return request.headers.get("x-api-key", get_remote_address(request))

limiter = Limiter(key_func=key_func)

Limiting purely by IP address (the slowapi default) is a real, meaningful gap for a real B2B API — GreenDesk's own real backend servers calling this endpoint would all share the same, real outbound IP address, so IP-based limiting would incorrectly throttle every legitimate client together. Keying the real rate limit by API key instead correctly scopes the limit per actual client.

A real, complete picture: two, genuinely separate concerns

text
Authentication (verify_api_key): answers "is this caller allowed to
  use this endpoint at all?"
Rate limiting (@limiter.limit): answers "is this caller making
  requests at a real, sane, sustainable rate?"

A real, secure API needs both, and they're genuinely independent — a real, valid, authenticated client can still exceed a sane real rate limit (through a bug or genuine burst of usage), and rate limiting alone, without authentication, still leaves the endpoint open to anyone who simply stays under the real per-minute cap.

Testing this real setup

bash
curl -X POST http://localhost:8000/qualify-lead \
  -H "x-api-key: a-real-valid-key" \
  -H "Content-Type: application/json" \
  -d '{"company_size": "50", "use_case": "team coordination", "budget_range": "$800/mo"}'

A real request missing the x-api-key header, or including an invalid one, now correctly receives a 401 rather than reaching the actual AI logic — genuinely verifiable with the exact same curl command, simply omitting or altering the header.

Next: building a real AI text summarizer with Python — a genuinely different real use case, condensing long real text rather than holding a conversation.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous6. Build an AI REST API with FastAPInext →8. Build an AI Text Summarizer with Python