019 · API · REST · CONTRACTS

API Design

Principles for building APIs that are intuitive, stable, and easy to consume.

If you are new here: An API (Application Programming Interface) is how one program asks another for data or work over the network. Your mobile app or website is usually the client; the server (or service) answers with HTTP responses. The contract is everything both sides rely on: paths, JSON shapes, status codes, and error formats. API design is the discipline of making that contract clear and stable so updates do not accidentally break every installed app.

TermPlain meaning
ClientAnything calling your API
Server / serviceThe program handling the request
ContractDocumented rules clients and servers share
Breaking changeA change that requires client updates (rename/remove field, change type)

The Problem

It's 2 AM. Crash reports spike because someone renamed a JSON field from user_id to userId. The server is healthy, but old app builds still expect user_id. That is a contract failure: the server changed the rules without a version or migration path clients could follow.

In plain terms: API design is how you keep clients and servers agreeing on paths, payloads, errors, and evolution — so shipping does not silently brick apps in the wild.

That is the problem this lesson exists to solve: predictable contracts instead of “it works if everyone deploys at once.”

SymptomWhat usually happened
Clients crash on parseField removed or renamed without a new API version
Chaos in retries200 responses that secretly contain errors
Slow incidentsErrors are only human sentences—no code or requestId

Everything below is how to avoid that class of failure.

Resources as nouns

Model your world as resourcesthings you can name—not as RPC-style URLs that encode the action in the path.

RPC-style (harder to reason about)Resource-style (common REST pattern)
GET /getUser?id=1GET /users/1
POST /createOrderPOST /orders
GET /fetchAllProductsGET /products
PathMeaning
/usersCollection—list users, or create a new user (with POST)
/users/42Single item—one user by id

Predictable URLs help caching, routing, logs, and onboarding: people guess right before reading your code.

Example — same user, two operations:

GET /users/42 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbG…
HTTP/1.1 200 OK
Content-Type: application/json
 
{"id":"42","display_name":"Asha","email":"[email protected]"}

Creating a user hits the collection; the server chooses the new id:

POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json
 
{"display_name":"Ben","email":"[email protected]"}
HTTP/1.1 201 Created
Location: /users/43
Content-Type: application/json
 
{"id":"43","display_name":"Ben","email":"[email protected]"}

Verbs mean behavior

HTTP methods (verbs) tell intermediaries and clients what kind of operation this is. The ecosystem assumes:

MethodTypical useImportant habit
GETRead dataShould be safe (no lasting side effects)
POSTCreate or start a processRepeats may duplicate—use idempotency keys for payments
PUTReplace a resource fullyOften treated as idempotent
PATCHPartial updateDocument exactly what “partial” means
DELETERemoveOften idempotent at the HTTP level

If GET deletes data or POST only reads, caches and retry logic will lie to you—bugs show up under load, not in demos.

Status codes tell the story

The status code is the first thing automated clients use. Use it honestly.

RangeMeaning
2xxSuccess
3xxRedirect—client should follow rules in headers
4xxCaller problem—fix request, auth, or permissions
5xxServer problem—retry with backoff may help
CodeUse it when
200 / 201 / 204Successful read / create / no body
400Validation or malformed input
401Not logged in / bad token
403Logged in but not allowed
404Resource does not exist
409Conflict—duplicate, stale version
429Rate limited
503Temporary overload or maintenance

Never return 200 with only {"error": "..."} in the body—clients and caches will treat it as success.

Example — wrong vs right:

HTTP/1.1 200 OK
Content-Type: application/json
 
{"error": "User not found"}

Better:

HTTP/1.1 404 Not Found
Content-Type: application/json
 
{"error":{"code":"USER_NOT_FOUND","message":"No user for that id.","requestId":"req_9c1"}}

Errors machines can parse

Structured errors include a stable machine code, a message for humans, optional field for validation, and a requestId that matches your logs and traces.

{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "No user exists for that id.",
    "requestId": "req_8f3a2b"
  }
}
PieceWhy it matters
codeSDKs and apps branch without parsing English
messageSupport and UI copy
fieldForm-level validation feedback
requestIdOne-click correlation in observability tools

Example — validation with a field pointer:

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Email format is invalid.",
    "field": "email",
    "requestId": "req_3d7f91"
  }
}

Versioning signals intent

Breaking changes—removing fields, changing types, renaming keys—must be obvious in your API surface. Silent breaks strand mobile users until they update the app.

StrategyTrade-off
URL /v1/..., /v2/...Very visible; easy in logs and docs
Header API-VersionClean URLs; caches must be configured carefully
Package / date versioningCommon for large public platforms

See the API Versioning lesson for deprecation timelines and additive patterns. Rule of thumb: old clients keep working until they explicitly move.

Example — same resource, two major versions:

Client built forCalls
Old appGET https://api.example.com/v1/users/42 → JSON uses user_id
New appGET https://api.example.com/v2/users/42 → JSON uses id

Or one URL with a header:

GET /users/42 HTTP/1.1
Host: api.example.com
API-Version: 2025-01-01

OpenAPI as the written contract

OpenAPI describes endpoints, parameters, bodies, and responses in machine-readable YAML/JSON. Teams use it for docs, code generation, contract tests, and breaking-change detection in CI.

ArtifactWhat it enables
Checked-in openapi.yamlReviewable diffs for API changes
Generated SDKsClients stay aligned with the server
Mock serversFrontend work before backend is done

The spec is not bureaucracy—it is how multiple teams ship without breaking each other.

Example — tiny OpenAPI fragment (YAML):

paths:
  /users/{id}:
    get:
      summary: Get one user
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
        "404":
          description: Not found

Your real file grows to list every path your API exposes; CI can diff two versions and flag removed fields.

Trade-offs

Upfront costLong-term win
Consistent nouns + verbsTooling and caching behave correctly
Real status codesSensible retries and monitoring
Structured errorsFaster debugging and better UX
Explicit versionsSafer evolution for mobile and enterprise
OpenAPISingle source of truth for the contract

Why this matters for you

Design as if you do not control every client. Field renames are cheap in git; they are expensive in production. Next lesson: REST APIs—pagination, caching, and idempotency in practice.

DIAGRAMDrag nodes · pan · pinch or double-click to zoom
FRAME 1 OF 8

Even with an OpenAPI file checked in, ad-hoc path names still ship when the spec is not enforced in code review — chaos wins.