Skip to main content

API Design

One-line summary: A good API is a clear, stable, and evolvable contract โ€” pick the right protocol, model resources well, and handle versioning, errors, and auth deliberately.


๐Ÿงฉ Core Conceptsโ€‹

REST Principlesโ€‹

REST (Representational State Transfer) models the system as resources identified by URLs, manipulated with standard HTTP verbs.

  • Resource-oriented URLs โ€” nouns, not verbs: /users/123/orders, not /getUserOrders.
  • HTTP verbs โ€” GET (read), POST (create), PUT (replace), PATCH (partial update), DELETE (remove).
  • Statelessness โ€” each request carries all context; no server-side session between calls (enables horizontal scaling).
  • Correct status codes โ€” communicate outcome via HTTP semantics.
  • Cacheability โ€” use Cache-Control, ETag, and conditional requests.
flowchart LR
Client -->|GET /users/123| API
Client -->|POST /users| API
Client -->|PATCH /users/123| API
Client -->|DELETE /users/123| API
API --> DB[(Data Store)]

REST vs. GraphQL vs. gRPCโ€‹

DimensionRESTGraphQLgRPC
Transport / formatHTTP + JSONHTTP + JSONHTTP/2 + Protobuf (binary)
ContractOpenAPI (optional)Strong schema (SDL)Strong schema (.proto)
Data fetchingFixed endpoints; over/under-fetchClient selects exact fieldsFixed RPC methods
Round tripsOften severalSingle query, many resourcesOne per call; streaming supported
StreamingLimited (SSE/WebSocket)SubscriptionsFirst-class (uni/bi-directional)
Browser supportNativeNativeNeeds gRPC-Web proxy
Best forPublic/CRUD APIs, cachingFlexible clients, aggregationLow-latency internal microservices

Rule of thumb: REST for public and cache-friendly APIs, GraphQL when diverse clients need flexible/aggregated data, gRPC for high-performance internal service-to-service calls.

API Versioning Strategiesโ€‹

StrategyExampleProsCons
URI path/v1/usersSimple, visible, cache-friendlyURL churn; not RESTful-purist
Query param/users?version=1Easy to defaultEasy to overlook; caching quirks
HeaderAccept: application/vnd.api.v1+jsonClean URLsHarder to test/discover
  • Prefer additive, backward-compatible changes (add fields, never remove/rename).
  • Version only on breaking changes; document a deprecation policy with timelines.

Pagination: Offset vs. Cursorโ€‹

flowchart TB
subgraph Offset[Offset / Limit]
O1[GET /items?limit=20&offset=40]
end
subgraph Cursor[Cursor / Keyset]
C1[GET /items?limit=20&after=eyJpZCI6MTIzfQ]
end
AspectOffset/LimitCursor/Keyset
EaseVery simpleSlightly more complex
Performance at depthDegrades (skips rows)Constant (indexed seek)
StabilityItems shift if data changesStable across inserts/deletes
Random page jumpYesNo (sequential)

Use offset for small, admin-style tables; use cursor for large, frequently-changing, or infinite-scroll feeds.

Idempotencyโ€‹

GET, PUT, and DELETE are naturally idempotent; POST is not. Make unsafe retries safe with an idempotency key:

POST /payments
Idempotency-Key: 5f3c9a1e-...-8b2d

The server stores the key + result; a retry with the same key returns the original response instead of charging twice.

Error Handling & Status Codesโ€‹

RangeMeaningCommon codes
2xxSuccess200 OK, 201 Created, 202 Accepted, 204 No Content
3xxRedirection301 Moved, 304 Not Modified
4xxClient error400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable, 429 Too Many Requests
5xxServer error500 Internal, 502 Bad Gateway, 503 Unavailable, 504 Timeout

Return structured, consistent error bodies (e.g., RFC 7807 Problem Details):

{
"type": "https://api.example.com/errors/validation",
"title": "Validation failed",
"status": 422,
"detail": "email must be a valid address",
"instance": "/users",
"errors": [{ "field": "email", "message": "invalid format" }]
}

๐Ÿ” Authentication & Authorizationโ€‹

  • API keys โ€” simple shared secret per client; good for server-to-server and identifying callers, but coarse-grained. Send via header, never in the URL.
  • OAuth 2.0 โ€” delegated authorization. A client obtains a scoped access token from an authorization server to act on a user's behalf without seeing credentials.
  • JWT โ€” a signed, self-contained token (header.payload.signature). Enables stateless auth: the server verifies the signature without a session lookup. Keep them short-lived and pair with refresh tokens.
sequenceDiagram
participant U as User
participant C as Client App
participant A as Auth Server
participant R as Resource API
U->>C: login
C->>A: request token (OAuth2)
A-->>C: access token (JWT)
C->>R: GET /data (Authorization: Bearer JWT)
R->>R: verify signature + scopes
R-->>C: 200 OK

Authentication = who you are. Authorization = what you're allowed to do (scopes, roles, RBAC/ABAC).

Rate Limitingโ€‹

Protect the API from abuse and overload with quotas. Communicate limits via headers (X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After) and return 429 Too Many Requests when exceeded. See Rate Limiting for token bucket, leaky bucket, and sliding window algorithms.

HATEOASโ€‹

Hypermedia As The Engine Of Application State โ€” responses include links to related actions, letting clients navigate the API without hardcoding URLs:

{
"id": 123,
"status": "pending",
"_links": {
"self": { "href": "/orders/123" },
"cancel": { "href": "/orders/123/cancel", "method": "POST" }
}
}

It maximizes discoverability and decoupling but adds payload weight and is rarely fully adopted in practice.


โš–๏ธ Trade-offs / When to Useโ€‹

Choose...When...
RESTPublic API, CRUD, HTTP caching matters
GraphQLMany client shapes, aggregation, avoid over-fetch
gRPCInternal, low-latency, streaming, strong contracts
Cursor paginationLarge/volatile datasets, infinite scroll
JWT (stateless)Horizontal scaling, no shared session store
API keysSimple machine-to-machine identification

Interview Questionsโ€‹

  • How do you design an API to be evolvable without breaking existing clients?
  • Explain when you'd use cursor pagination vs offset pagination and why.
  • Describe strategies to make POST/transactional endpoints idempotent.

Production Checklistโ€‹

  • Document versioning and deprecation policies; provide migration guides
  • Enforce API contracts with OpenAPI / protobuf and run schema validation in CI
  • Monitor API latency, error rates, and rate-limit rejections
  • Provide consistent error payloads and instrument client/consumer SDKs where possible

Testing & Monitoringโ€‹

  • Run contract tests (consumer-driven) and API contract validation in CI
  • Load test common endpoints and simulate spike traffic, ensuring caches and CDNs behave correctly
  • Validate idempotency keys and replay behavior under retries

โ† Back to System Design ยท ยฉ sparshjaswal