API Stack
How services expose and consume each other — the transports, the identity layer, and the reliability concerns that decide whether an interface survives real traffic and years of change.
Click any concept to expand it.
Protocols and transports
REST
REST models an API as resources addressed by URL and manipulated with standard HTTP verbs — GET to read, POST to create, PUT/PATCH to update, DELETE to remove — with status codes carrying the outcome.
Its durability comes from using the web's existing semantics rather than inventing new ones: caching, proxies, and status handling all work without special support. The common failure is treating REST as "JSON over HTTP" and losing the properties that made it worth choosing.
GraphQL
GraphQL exposes a single endpoint backed by a typed schema, and lets the client specify exactly which fields it wants in one request. It solves over-fetching and the "three round trips to render one screen" problem that REST invites.
The costs land on the server. Caching is harder without per-URL granularity, an arbitrarily deep query can be expensive, and resolver design decides whether a page load is one database query or a hundred. Query depth limits and cost analysis are not optional on a public GraphQL API.
gRPC
gRPC uses Protocol Buffers over HTTP/2 for binary, strongly-typed remote calls, with client and server code generated from a shared .proto definition. It supports streaming in both directions.
It is the usual choice for internal service-to-service traffic where both ends are yours: the payloads are compact, the contract is enforced at compile time, and the performance is materially better than JSON. It is a poor fit for public browser-facing APIs, where reach and debuggability matter more than efficiency.
WebSockets
A WebSocket upgrades an HTTP connection into a persistent, bidirectional channel, so the server can push data without the client polling for it. It is the natural transport for live updates, collaborative editing, and streaming model output token by token.
The trade is statefulness. A long-lived connection has to be tracked, reconnected, authenticated at open, and load-balanced with affinity — all things stateless HTTP gave you for free.
Webhooks
A webhook inverts the direction of integration: instead of you polling a provider for changes, the provider POSTs to a URL you register when something happens. It removes the latency and waste of polling.
Receiving them correctly requires more care than sending. You must verify the signature (or anyone can forge events), tolerate duplicates, and not assume ordering — most providers guarantee at-least-once delivery and nothing about sequence.
OpenAPI & Schemas
OpenAPI is a machine-readable description of an HTTP API — endpoints, parameters, request and response shapes, error codes. From it you can generate documentation, client SDKs, mock servers, and contract tests.
The real value is that the schema becomes the single source of truth. Hand-written docs drift from the implementation within weeks; a generated client cannot, because it is derived from the same definition the server validates against.
Identity and access
Authentication
Authentication answers who is calling — verifying identity through credentials, tokens, or a federated provider before any decision about permissions is made.
Most real-world failures are not in the verification itself but around it: tokens with no expiry, secrets in logs or URLs, sessions that never invalidate on password change. Getting lifetime and storage right is most of the work.
Authorization
Authorization answers what this identity may do — enforced per request, per resource, usually through role-based or attribute-based policy.
Two rules cover most breaches. Enforce it server-side at every entry point, because client-side checks are advisory. And check ownership, not just role: a valid user requesting another user's record is the classic broken-access-control bug, and it passes authentication cleanly.
OAuth 2.0
OAuth lets a user grant an application scoped, revocable access to their data on another service without handing over their password. The application receives a token limited to specific permissions, and the user can revoke it independently.
It is an authorization framework, not an authentication one — a distinction that has caused real vulnerabilities. OpenID Connect is the layer added on top when what you actually want is "prove who this user is", which is what single sign-on relies on.
API Keys
An API key is a bearer credential identifying a calling application rather than a user. Whoever holds it can use it, which makes it simple to issue and simple to leak.
Because possession is authorisation, the operational requirements are scoping, rotation, and a fast revocation path. A leaked key is a standing grant until someone revokes it — and keys committed to repositories are among the most reliably exploited credentials in existence.
Reliability at scale
Rate Limiting
Rate limiting caps how many requests a client may make in a window, protecting the service from overload, abuse, and a single misbehaving integration consuming everyone's capacity.
Good implementations are informative rather than merely restrictive: return 429 with a Retry-After header and remaining-quota headers, so a well-behaved client can back off correctly instead of hammering and being throttled harder.
Idempotency
An operation is idempotent when performing it twice has the same effect as performing it once. GET and DELETE are naturally idempotent; POST is not, which is where the trouble lives.
Networks make this unavoidable rather than theoretical: a timed-out request may have succeeded. Without an idempotency key derived from the logical action, the retry that follows creates a second order or a second charge — a correctness bug, not a performance one.
Retries & Backoff
Retries handle transient failure — a dropped connection, a brief 503. Done naively they cause the outage they were meant to survive, as every client retries in unison against a struggling service.
The correct shape is exponential backoff with jitter, a bounded attempt count, and retrying only what is safe to repeat. Retrying a non-idempotent write is not resilience; it is duplication.
Caching & ETags
HTTP caching lets responses be reused rather than recomputed. Cache-Control states how long a response stays fresh; an ETag is a version identifier a client can send back to ask "has this changed?" and receive a cheap 304 Not Modified.
The fastest request is the one never made, and the second fastest returns no body. Correct cache headers routinely deliver more improvement than optimising the handler behind them.
Gateways & Load Balancing
An API gateway is the single front door: it terminates TLS, authenticates, applies rate limits, routes to the right service, and emits consistent logs and metrics. A load balancer distributes traffic across instances behind it.
Concentrating cross-cutting concerns here means individual services stop reimplementing authentication and throttling — at the cost of a component that must itself be highly available, since everything depends on it.
Contract and evolution
Pagination
Pagination returns large collections in bounded pages. Offset-based paging (?page=3) is simple but skips or repeats rows when the underlying data changes between requests. Cursor-based paging encodes a stable position and does not.
Any endpoint returning a collection needs it from day one. Adding pagination later is a breaking change, and the endpoint that was fine at a thousand rows is an incident at a million.
Versioning
Versioning lets an API change without breaking existing consumers — through the path (/v2/), a header, or a dated version identifier. The point is that clients you cannot deploy for keep working.
The discipline is knowing what actually breaks. Adding an optional field is safe; removing one, renaming it, tightening validation, or changing a default is not. A deprecation policy with a real timeline matters more than the versioning scheme you pick.
Error Handling & Status Codes
Errors are part of the interface. The status code carries the category — 4xx for "you sent something wrong", 5xx for "we failed" — and the body should say precisely what went wrong and whether retrying could help.
Two failure modes are common and both are expensive. Returning 200 with an error inside the payload defeats every piece of tooling that reads status codes. Returning a bare 400 with no detail turns every integration bug into a support ticket.
