What is it? #
REST is a style for designing APIs around resources — nouns — manipulated with standard HTTP methods.
Its value is predictability. If your API follows the conventions, a developer can guess most of it correctly after seeing two endpoints.
The core ideas are small: resources have URLs, methods describe the action, status codes describe the outcome, and responses are consistent in shape.
Most of the difficulty is in the details that show up later: pagination, filtering, error formats, versioning and how you change things without breaking clients.
Think of it like this #
A well-organised shop. Aisles are labelled by product type, prices are on the shelf, and the checkout works the same everywhere.
You do not need a guided tour because the layout follows conventions you already know. An API that invents its own conventions requires a tour for every visitor.
Simple example #
An orders API. Listing, filtering, paginating, creating, updating and cancelling orders, with predictable URLs, status codes and error shapes.
Code #
Resources and methods
GET /orders list, with filters and pagination
POST /orders create → 201 + Location header
GET /orders/A-1 fetch one → 200 or 404
PATCH /orders/A-1 partial update → 200
DELETE /orders/A-1 delete → 204 (no body)
GET /orders/A-1/lines a sub-resource
POST /orders/A-1/cancel an action that is not CRUD — acceptable
Nouns, plural, lowercase, hyphenated. Never verbs in the path:
BAD /getOrder?id=A-1 /createNewOrder /order_delete
GET /orders?status=paid&created_after=2026-09-01&limit=20&cursor=eyJpZCI6...
200 OK
{
"data": [
{ "id": "A-1", "status": "paid", "total": 2499.0, "currency": "INR" }
],
"pagination": {
"next_cursor": "eyJpZCI6IkEtMjEifQ",
"has_more": true
}
}
A consistent error shape, used everywhere
400 Bad Request
{
"error": {
"code": "validation_failed",
"message": "The request could not be processed",
"details": [
{ "field": "quantity", "issue": "must be greater than 0" },
{ "field": "sku", "issue": "unknown product" }
],
"request_id": "req_01H9..."
}
}
Machine-readable code, human-readable message, per-field details,
and a request ID so support can find it in the logs.
Status codes, used honestly
200 OK read or update succeeded
201 Created new resource; include a Location header
202 Accepted accepted for async processing, not yet done
204 No Content success with nothing to return, e.g. delete
400 Bad Request malformed or invalid input
401 Unauthorized no or invalid credentials
403 Forbidden authenticated but not permitted
404 Not Found no such resource (also used to hide existence)
409 Conflict clashes with current state, e.g. duplicate
422 Unprocessable syntactically valid but semantically wrong
429 Too Many Requests rate limited; include Retry-After
500 Internal Error your fault; never leak a stack trace
How it works #
Resource naming is the foundation. Plural nouns with the method carrying the verb means DELETE /orders/A-1 needs no explanation. Verb-based paths force the reader to learn each one.
Cursor pagination is used here rather than page=2. Offset pagination breaks when rows are inserted between requests — items shift and get skipped or duplicated. A cursor encodes a position, so results stay stable.
Wrapping the list in a data key leaves room to add pagination and metadata later without changing the response type. Returning a bare array paints you into a corner.
One error shape everywhere is what makes client code simple. A machine-readable code lets clients branch; the message is for humans; details supports form validation; request_id connects a user's complaint to your logs in seconds.
Status codes must be honest. Returning 200 with an error body forces every client to parse the body to know what happened, and defeats caches, proxies and monitoring that count 5xx responses.
For actions that are not CRUD — cancel, refund, publish — a sub-resource POST such as /orders/A-1/cancel is a pragmatic and widely used compromise. Forcing everything into pure resource semantics usually produces worse APIs.
Versioning belongs in the plan from the start: /v1/orders, or a version header. Within a version, only make additive changes — new optional fields are safe, removing or renaming a field is not.
Real-world use #
Public APIs live or die by their documentation and consistency. OpenAPI specifications generate documentation, client libraries and request validation from one definition, which is why most teams maintain one.
Rate limiting, idempotency keys and correlation IDs, covered in earlier lessons, are all part of a well-behaved API surface.
Breaking changes are the recurring operational problem. Once external clients exist, you cannot rename a field; you add the new one, support both, announce a deprecation timeline and remove the old one later.
Alternatives exist and have their place. GraphQL suits clients that need widely varying data shapes; gRPC suits high-volume internal service calls. REST remains the default for public HTTP APIs because it is universally understood.
The most practical test of an API design is whether a competent developer can use it correctly without asking you a question.
Common mistakes #
- Verbs in URLs, such as /getOrders or /createUser.
- Returning 200 with an error inside the body.
- Offset pagination on data that changes, causing skipped or duplicated rows.
- Different error shapes on different endpoints.
- Breaking changes without versioning or a deprecation period.
Practice #
Design the endpoints for a task management API: list with filters and pagination, create, fetch, update, delete, and mark complete. Write the URL, method, status code and response shape for each, plus one error example. Then list three changes that would break existing clients.