Part 1 · 2 chapters · ~12 min

REST Properly

HTTP methods and their semantics, a consistent status code set, caching with ETag and Cache-Control, optimistic concurrency with If-Match, asynchronous operations with 202 and status resources, bulk operations, and OpenAPI as the contract.

3

HTTP doing the work

code
POST /v1/transfers                         Idempotency-Key: 7c1e…
201 Created   Location: /v1/transfers/tr_8Jk2   { "id": "tr_8Jk2", "state": "pending", ... }

GET /v1/transfers/tr_8Jk2                  If-None-Match: "v3"
304 Not Modified

PATCH /v1/beneficiaries/bn_77              If-Match: "v5"    { "nickname": "Mum" }
412 Precondition Failed                    (someone changed it since you read v5)

POST /v1/statements                        { "account_id": "ac_1", "month": "2026-09" }
202 Accepted  Location: /v1/operations/op_55   → poll until { "status": "done", "result": "/v1/statements/st_9" }
REST, PROPERLY
the parts of HTTP that do real work
methodsGET safe; PUT and DELETEidempotent; POST neither; PATCHpartial updates.status codes201 + Location, 202 for async, 400vs 422, 401 vs 403, 404, 409, 429.cachingCache-Control, ETag andIf-None-Match for reads; 304 savesbandwidth.concurrencyETag + If-Match on updates: 412when someone else changed itfirst.async202 Accepted + a status resourceto poll, or a webhook oncompletion.hypermediaLinks to next actions areoptional; consistent URLs and docsmatter more.
swipe the figure sideways, or tap expand for full screen
1/6
methods
Use methods for their semantics: GET never changes state (crawlers and prefetchers call it), PUT and DELETE can be retried safely, POST creates or triggers actions and needs an idempotency key for safe retries.
semantics: safe, idempotent, neitherPOST needs idempotency keys
4

OpenAPI as the contract

code
# openapi.yaml (excerpt): the source of truth for docs, SDKs, validation and contract tests
paths:
  /v1/transfers:
    post:
      operationId: createTransfer
      parameters: [ { name: Idempotency-Key, in: header, required: true, schema: { type: string, maxLength: 64 } } ]
      requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/TransferCreate' } } } }
      responses:
        '201': { description: created, content: { application/json: { schema: { $ref: '#/components/schemas/Transfer' } } } }
        '422': { $ref: '#/components/responses/Problem' }

Spec-first or code-first? Spec-first (write OpenAPI, review it, generate server stubs and clients) makes the contract the design artifact. Code-first (generate the spec from annotated code) is faster but tends to let implementation details leak into the contract. For public and partner APIs, spec-first reviews pay for themselves.