REST APIs
Resources, status codes and versioning — and the OpenAPI contract that keeps clients honest.
Nouns, Verbs And Meaning
The core idea is that URLs identify resources (nouns) and HTTP methods supply the verbs. POST /createOrder works, but it throws away every guarantee the protocol offers: caches, retries and proxies all understand GET and PUT, and understand nothing about your verb.
| Method | Meaning | Safe | Idempotent |
|---|---|---|---|
| GET | Read | Yes | Yes |
| POST | Create, or an action | No | No |
| PUT | Replace at a known id | No | Yes |
| PATCH | Partial update | No | Not necessarily |
| DELETE | Remove | No | Yes |
Idempotent means repeating the call leaves the same state — which is what makes a client safe to retry after a timeout. For non-idempotent creates, accept an Idempotency-Key header and store the result against it; that single mechanism prevents the duplicate-charge class of bug.
Status Codes, Errors & Pagination
Status codes are the part clients actually branch on, so returning 200 with {"error": …} inside breaks every generic client, proxy and monitoring tool at once.
| Code | Means |
|---|---|
| 200 / 201 | Done / created, with a Location header |
| 202 | Accepted, still processing |
| 400 | Malformed — do not retry unchanged |
| 401 / 403 | Not authenticated / authenticated but not allowed |
| 404 / 409 | No such resource / conflicts with current state |
| 422 | Well formed but semantically invalid |
| 429 | Rate limited — send Retry-After |
| 500 / 503 | We broke / temporarily unavailable, retry later |
// a machine-readable error body: code for logic, message for humans
{
"error": {
"code": "insufficient_funds",
"message": "Balance is below the transfer amount.",
"details": { "available": 1240, "requested": 5000 }
}
}
Paginate every collection from day one. Offset paging is simple and drifts when rows are inserted mid-scan; cursor paging is stable and is what any endpoint over a growing table should use. Retrofitting pagination onto a live endpoint is a breaking change.
Versioning & Compatibility
Clients you do not control will keep calling the shape you shipped. The discipline is to make additive changes forever and version only when you genuinely cannot.
| Change | Breaking? |
|---|---|
| Adding an optional field to a response | No — clients must ignore unknown fields |
| Adding an optional request parameter | No |
| Removing or renaming a field | Yes |
| Tightening validation | Yes — previously accepted calls now fail |
| Changing a type, unit or default | Yes, and the worst kind: silent |
| Strategy | Trade-off |
|---|---|
/v1/ in the path | Obvious, cacheable, ugly — the common choice |
| Version header | Cleaner URLs, easier to get wrong in a client |
| No versioning, additive only | Cleanest when you can hold the line |
OpenAPI & Contracts
An OpenAPI document describes the API in machine-readable form: paths, parameters, schemas, responses and auth. Written by hand or generated from code, it becomes the artefact everything else hangs off.
paths:
/orders/{id}:
get:
summary: Fetch an order
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
responses:
'200':
content:
application/json:
schema: { $ref: '#/components/schemas/Order' }
'404':
$ref: '#/components/responses/NotFound'
| Gives you | Instead of |
|---|---|
| Generated clients and server stubs | Hand-written HTTP calls per consumer |
| Request and response validation | Trusting that the docs are current |
| Interactive documentation | A wiki page from last year |
| Contract tests in CI | Finding the break in production |
| Mock servers | Front end blocked on the backend |
Interview Questions
What does idempotent mean, and why does it matter?
Repeating the request leaves the same state. It is what makes a client safe to retry after a timeout — and why non-idempotent creates need an idempotency key to avoid duplicates.
Why not return 200 with an error body?
Status codes are what clients, proxies, caches and monitoring branch on. Hiding failure inside a success breaks all of them and makes error rates invisible.
Offset or cursor pagination?
Cursor for anything over a growing table — offset paging skips or repeats rows when data is inserted mid-scan. Add pagination from the start; retrofitting it is a breaking change.
Which API changes are breaking?
Removing or renaming fields, tightening validation, and changing a type, unit or default. Adding optional fields and parameters is safe if clients ignore unknown fields.
What does OpenAPI actually buy you?
Generated clients and stubs, request and response validation, live documentation, mock servers, and contract tests that fail the build when code and spec diverge.
PUT or PATCH?
PUT replaces the whole resource at a known id and is idempotent. PATCH applies a partial change and is only idempotent if the operations themselves are.