"Once your API is in production with real consumers, you have a new set of problems: cross-cutting policy (auth, rate limit, observability), version evolution (without breaking integrations), and deprecation discipline (telling consumers what's going away). Gateways handle the first; lifecycle hygiene handles the rest."
What an API Gateway Does
An API gateway sits in front of one or more services and handles the cross-cutting concerns every API needs but no application should re-implement per service:
- Auth verification — validates Bearer tokens, JWT signatures, API keys before requests reach your app.
- Rate limiting — per-user, per-API-key, per-endpoint limits enforced centrally.
- Request/response transformation — rename headers, rewrite paths, modify payloads for legacy compatibility.
- Observability — log every request, emit metrics, trace propagation, all without app changes.
- Version routing — route
/v1/*to one service,/v2/*to another. - TLS termination — handle certificate management at the edge; app receives plain HTTP.
- Caching — gateway-level response caching for hot endpoints.
Major options: Kong (open-source + enterprise), AWS API Gateway, Azure API Management, Google Apigee, Cloudflare Workers + Cache, Tyk. Pick by cloud + scale + feature set.
The Single-Service Alternative
For small services or solo development, a gateway is overkill. Cross-cutting concerns can live in middleware (FastAPI's add_middleware, Express's app.use). The decision flips when:
- You have multiple services and want consistent policy across them.
- You want to swap implementations (replace a Python service with a Go service) without changing client URLs.
- You need centralized API key management across many endpoints.
- Your auth/rate-limit policies are complex enough to deserve dedicated software.
Start without a gateway; add one when the duplication of cross-cutting code across services becomes the bigger pain.
Versioning Lifecycles — The Long-Term Game
Once you've shipped an API to real consumers, each version is a commitment that takes years to retire. A healthy lifecycle:
- Beta — early access, possibly breaking changes. Document the instability; expect early adopters only.
- Stable — committed contract. Additive changes only; breaking changes go to the next major version.
- Deprecated — signaled via
Deprecation: <date>response header months before sunset. Still works; consumers should migrate. - Sunset announced —
Sunset: <date>header on every response months in advance. The specific date the endpoint will stop responding. - Retired — endpoint returns
410 Goneor redirects to the successor version (308 with Location).
Stripe runs this playbook expertly: every version has a date stamp, every change has a migration guide, every retired endpoint returns 410 with a clear error message pointing at the new version. Consumers always know where they stand.
The Sunset Header in Practice
# Endpoint still working but marked for retirement
GET /v1/users HTTP/1.1
HTTP/1.1 200 OK
Deprecation: Sun, 01 Jan 2026 00:00:00 GMT
Sunset: Wed, 01 Jul 2026 00:00:00 GMT
Link: </v2/users>; rel="successor-version"
Content-Type: application/json
[...]
# After Sunset date — endpoint returns 410
GET /v1/users HTTP/1.1
HTTP/1.1 410 Gone
Link: </v2/users>; rel="successor-version"
Content-Type: application/json
{"error":{"code":"endpoint_retired","message":"Use /v2/users; see https://docs.example.com/migrate-v1-v2"}}
Headers signal machine-readable; the body carries the human-readable migration breadcrumb.