Saltar al contenido principal
How systems communicate — TCP/IP, HTTP, DNS, load balancing, and security.

Networking

How systems communicate — TCP/IP, HTTP, DNS, load balancing, and security.

API Design & Integration

The API Is a Contract

An API is the boundary where your system meets the world — and like any contract, its failure modes show up later: a client you can’t evolve, a field you can’t change, an endpoint that breaks every mobile release. Most API pain is not “we couldn’t build it” — it’s “we couldn’t change it.” So this topic is about designing for evolution: versioning, pagination, idempotency, error semantics, and knowing when REST isn’t the right shape at all.

The litmus test for every API decision: “In six months, when this changes, how many clients break?” If the answer is “many,” the design is wrong.

Resource Modeling

From the HTTP topic you have the verbs; the noun side is where modeling happens:

  • Noun resources, not verbs — /orders, /orders/42; never /getOrder, /createOrder.
  • Nested resources for containment — /orders/42/items (items belong to the order), but avoid going too deep — flatten with query params (/orders?status=open) when nesting becomes a maze.
  • Sub-resources as concepts — /users/42/subscriptions, /orders/42/cancel should really be a state change: POST /orders/42/status with a body, or a dedicated action endpoint only when a method can’t express it.
  • Consistent collection semantics — GET /orders → [ ... ]; GET /orders/42 → single object; POST /orders → 201 + Location; DELETE /orders/42 → 204.

Versioning: The Evolution Strategy

You will break clients. The question is whether you break them gracefully:

  • URL versioning — /v1/orders, /v2/orders. Obvious, cache-friendly, unforgettably visible in logs. The default for public APIs.
  • Header versioning — Accept: application/vnd.example.v2+json. Clean URLs, but invisible in URL-based analytics and easy to forget.
  • Query-param versioning — ?version=2. Simple, but pollutes cache keys.

Operational guidance: version when the contract changes semantically (a field meaning changes, a field is removed, required fields change). Additive changes — new optional fields, new endpoints — do not require a version bump, and disciplined additive evolution is how APIs avoid a /v9. Deprecate loudly (Deprecation header, sunset dates) and remove only when traffic metrics say it’s safe.

Pagination: Don’t Dump the Whole Table

Unbounded GET /orders is how you accidentally DoS yourself on day one of real traffic. Two shapes:

Page-based (?page=2&size=50)Cursor-based (?cursor=eyJpZCI6...)
MeaningOffset windowOpaque pointer into the result set
Stable under inserts?No — items shift across pagesYes — cursors anchor to a position
Deterministic orderingOnly if sorted by unique keyBuilt into the cursor
Best forAdmin UIs, small dataFeeds, infinite scroll, large volatile sets

Rules that prevent most bugs: always return total counts/next links (next, previous, or a Link header) rather than making clients compute pagination math; sort by a unique key so page boundaries are well-defined; and cap the page size server-side (a requested size of 10,000 is a code smell — clamp it).

Idempotency and Retries

Networks drop requests. When a client retries a POST /payments, the server must not charge twice. The standard fix is an idempotency key:

  1. Client sends Idempotency-Key: <uuid> on the first attempt.
  2. Server processes, stores the key → response.
  3. If the client retries with the same key (after a timeout), the server returns the stored response instead of processing again.

This is why the method table in the HTTP topic matters: GET/PUT/DELETE are naturally idempotent and safe to retry; POST is not — so any retryable POST needs a key. Design the retry semantics into the API (document max retries, backoff, and key expiry) or your clients will reinvent them badly.

Structured Error Semantics

Error responses are the most-ignored, most-copied part of an API — and the one your clients actually code against. The shape that survives contact:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json

{
  "type": "https://api.example.com/problems/order-total",
  "title": "Order total below minimum",
  "status": 422,
  "detail": "Minimum order total is 10.00",
  "instance": "/orders/42",
  "field_errors": { "total": ["must be at least 10.00"] }
}

Rules: use the correct status class (client errors are never 5xx); give a machine-readable code plus a human detail; include a stable error type clients can switch on; include field-level errors for validation. And never return {"error": "something went wrong"} — that text belongs in your logs, not the client’s UI.

Beyond REST: gRPC and GraphQL

REST is the default, but not the answer to everything:

  • gRPC — HTTP/2 + Protocol Buffers. Strongly-typed schemas, binary (small + fast), first-class streaming and codegen. Ideal for internal service-to-service communication where you control both ends and need RPC semantics (see Distributed Systems). Trade-offs: binary debugging is harder, no browser-native JSON, awkward over restrictive networks.
  • GraphQL — one endpoint, clients select fields and relations. Great when clients are heterogeneous (mobile vs web) and you want to avoid N+1 round trips. Trade-offs: no HTTP caching on the envelope, complexity moves server-side, and the “one endpoint” model fights existing middleware.

The decision matrix:

NeedChoose
Public/human/tooling-friendly, cacheableREST
High-throughput internal service calls, streaminggRPC
Heterogeneous clients, field-selective queriesGraphQL
Event streams / pub-subAsync events (not request/response at all)

The anti-pattern is choosing by hype: a public SDK-less API as gRPC, or an internal CRUD service as GraphQL, usually signals the wrong trade-off.

Practice Trajectory

  1. Design a /orders resource model on paper: list the endpoints, methods, and status codes for create/read/update/delete/state-change.
  2. Implement cursor pagination on a test dataset and prove it stays stable while rows are inserted mid-pagination.
  3. Add an Idempotency-Key header to a mock POST /payments; retry the same key and confirm a single stored response.
  4. curl -i a real API that returns 422/429 and inspect whether its error body has a machine-readable code you could program against.
  5. Redesign one of your existing endpoints so it’s evolution-safe (additive-only + a versioning story) — and write the deprecation notice.

When It’s the Right Tool

SituationTakeaway
Public APIREST + versioning + cursor pagination + idempotency
Retry-heavy flows (payments, orders)Idempotency keys, always
Client debuggingStructured errors with types, not "error": true
Internal service meshgRPC
Mobile/web field-selectionGraphQL
Every decision“How many clients break when this changes?”