A REST API that is inconsistently designed creates a tax on every developer who uses it. Inconsistent resource naming, unexpected status codes, missing pagination, and opaque error messages all add up to hours of debugging and support requests that could have been avoided. Well-designed REST APIs, by contrast, are discoverable — developers can work with them from documentation alone and rely on their behavior being predictable.
This guide covers REST API design from first principles through production patterns: the Richardson Maturity Model, resource naming conventions, correct HTTP method and status code usage, error handling standards, pagination strategies, idempotency, versioning, and OpenAPI documentation. The goal is a set of design decisions you can apply consistently across every API endpoint.
REST API Design: The Richardson Maturity Model
The Richardson Maturity Model (RMM) provides a useful framework for evaluating and improving REST API design. It defines four levels of RESTfulness, each building on the previous.
Level 0: HTTP as a transport tunnel. All requests go to one endpoint as POST, with the operation type specified in the request body. XML-RPC and SOAP typically operate at this level. There are no resources, no HTTP method semantics, and no status code semantics.
Level 1: Resources. The API exposes multiple endpoints, each representing a distinct resource: /users, /products, /orders. Operations still use POST for everything, but the URL structure conveys meaning.
Level 2: HTTP verbs. The API uses HTTP methods according to their semantics: GET for reading, POST for creating, PUT and PATCH for updating, DELETE for removing. Status codes carry meaning: 200 for success, 404 for not found, 201 for created. This is where most production REST APIs operate — and where they should.
Level 3: HATEOAS. Responses include hyperlinks to related resources and available actions. Clients do not need to construct URLs — they navigate the API through links embedded in responses. This is theoretically the most RESTful level but is rarely implemented in practice because the additional complexity rarely justifies the benefit for APIs with stable, well-documented contracts.
Practical recommendation: target Level 2 for all APIs. The benefits of Level 2 (correct HTTP semantics, predictable status codes) are concrete and implementation-independent. Level 3 adds significant implementation overhead for a theoretical benefit that most API consumers don't need.
Resource Naming Conventions
Consistent resource naming is the single highest-leverage REST API design decision. It creates a mental model that developers apply across all endpoints without consulting documentation.
Use plural nouns for collection resources:
/usersnot/user/productsnot/product/orders/123/itemsnot/orders/123/item
Use lowercase with hyphens, not camelCase or underscores:
/user-profilesnot/userProfilesor/user_profiles
Hierarchical relationships use path nesting:
/users/123/orders— orders belonging to user 123/orders/456/items— items within order 456/teams/789/members/321— member 321 of team 789
Keep nesting to two levels maximum. /users/123/orders/456/items/789/reviews is too deep — it tightly couples the URL structure to the data model and makes URLs brittle when the model evolves.
Query parameters for filtering, sorting, and search:
/users?role=admin&status=active— filter by role and status/products?sort=-price,name— sort by price descending, then name ascending/users/search?q=jane— full-text search
Do not encode operations in URLs. /users/123/activate is acceptable for state transitions when no clean HTTP verb mapping exists. /users/delete/123 is not — DELETE /users/123 is correct.
HTTP Methods: Semantics and Idempotency
Use HTTP methods according to their defined semantics:
| Method | Semantics | Idempotent | Safe |
|---|---|---|---|
| GET | Retrieve resource | Yes | Yes |
| POST | Create resource | No | No |
| PUT | Replace resource entirely | Yes | No |
| PATCH | Update resource partially | Conditional | No |
| DELETE | Remove resource | Yes | No |
| HEAD | Same as GET, no response body | Yes | Yes |
| OPTIONS | List allowed methods | Yes | Yes |
Idempotent means calling the method multiple times with the same input produces the same result as calling it once. GET, PUT, and DELETE are idempotent — calling DELETE twice on the same resource produces the same end state (resource deleted) whether you call it once or ten times. POST is not idempotent — calling POST /orders twice creates two orders.
Safe means the method does not modify server state. Only GET and HEAD are safe.
These semantics matter for caching (GET responses are cacheable; POST responses are not by default), retry logic (idempotent methods are safe to retry on timeout; POST is not), and client library behavior.
PUT vs. PATCH: PUT replaces the entire resource — if you omit a field in a PUT request, that field is removed from the resource. PATCH applies a partial update — only the fields you include are changed. Use PUT when clients always provide the complete resource state; use PATCH when clients update specific fields.
HTTP Status Codes
Return the status code that accurately describes the outcome of the request:
2xx Success:
- 200 OK — successful GET, PUT, PATCH
- 201 Created — successful POST that creates a new resource; include
Locationheader pointing to the new resource - 204 No Content — successful DELETE or POST/PUT with no response body
4xx Client Error:
- 400 Bad Request — invalid syntax, malformed request
- 401 Unauthorized — authentication required (misleadingly named — it means unauthenticated)
- 403 Forbidden — authenticated but not authorized
- 404 Not Found — resource does not exist
- 409 Conflict — request conflicts with current state (duplicate creation, concurrent modification)
- 422 Unprocessable Entity — valid syntax but semantic validation failed (invalid field value)
- 429 Too Many Requests — rate limit exceeded; include
Retry-Afterheader
5xx Server Error:
- 500 Internal Server Error — unexpected server failure
- 503 Service Unavailable — temporary unavailability; include
Retry-Afterheader
The distinction between 401 and 403 matters: 401 means "provide credentials"; 403 means "your credentials are valid but you lack permission". Using 403 where 401 is correct prevents clients from retrying with different credentials.
The distinction between 400 and 422 matters: 400 is for syntactically malformed requests (invalid JSON, missing required headers); 422 is for requests that are syntactically valid but fail domain validation (account balance insufficient, date in the past).
Error Response Standard
Consistent error responses are as important as consistent success responses. The RFC 7807 Problem Details standard defines a structure that communicates enough context for clients to understand and act on errors:
{
"type": "https://api.example.com/errors/validation",
"title": "Validation Failed",
"status": 422,
"detail": "The request body contains invalid field values.",
"instance": "/orders",
"timestamp": "2026-04-01T10:30:00Z",
"request_id": "req_7f8b3c2d",
"errors": [
{
"field": "quantity",
"message": "Must be greater than 0",
"received": -1
},
{
"field": "shipping_address",
"message": "Required field missing"
}
]
}
Every error response should include:
- type: a URI identifying the error category (links to documentation)
- title: human-readable summary
- status: the HTTP status code (also in the body for clients that log response bodies without status codes)
- detail: specific information about this error instance
- request_id: a correlation ID for debugging — clients can include this when filing support requests
Field-level validation errors should enumerate all invalid fields in a single response, not return one error at a time. Returning validation errors one-by-one forces clients to make multiple round trips to fix a form.
Pagination, Filtering, and Sorting
Offset pagination is simple and works well for non-real-time data:
GET /products?offset=40&limit=20
Cursor pagination is stable in the presence of data changes:
GET /products?after=cursor_abc123&limit=20
Cursor pagination prevents the "skipped items" problem: if a product is deleted between page 2 and page 3, offset pagination skips the item that moved into its position. Cursor pagination, anchored to a stable cursor value, does not skip items.
Response metadata: always include pagination metadata in the response:
{
"data": [...],
"pagination": {
"total": 847,
"per_page": 20,
"next_cursor": "cursor_xyz789",
"has_more": true
}
}
For HATEOAS-aligned APIs, include next and previous links in the response headers or body.
Filtering: support common filter patterns consistently. Use query parameters for simple equality filters (?status=active), range filters (?created_after=2026-01-01), and multi-value filters (?category=books,electronics). Document all supported filter parameters — undocumented filters invite misuse and create implicit contracts.
Sorting: use sort with field names, and - prefix for descending: ?sort=-created_at,name (created_at descending, then name ascending). Avoid sort=date_desc or sort=3 — they are not discoverable.
Idempotency Keys for Safe Retries
POST requests are not idempotent. If a client sends a POST /orders request and the network times out before receiving a response, the client cannot know whether the server processed the request. Retrying without idempotency protection may create duplicate orders.
Idempotency keys solve this: the client generates a unique key (UUID) for each intended operation and sends it in an Idempotency-Key header:
POST /orders
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
{ "product_id": "prod_123", "quantity": 2 }
The server stores the key and the response. If the same key arrives again (client retry), the server returns the stored response without processing the request again. This makes the POST effectively idempotent from the client's perspective.
Idempotency keys should be stored with a TTL (24-48 hours). Use Redis for idempotency key storage — it provides fast lookups and automatic expiry.
Stripe, Plaid, and Braintree all implement idempotency keys for their payment APIs. For any API that creates durable state (orders, payments, account operations), idempotency keys are an important safety mechanism.
ETags and Conditional Requests
ETags allow clients to cache resources and make conditional requests that avoid unnecessary data transfer:
Conditional GET: client includes the ETag from a previous response in If-None-Match. If the resource hasn't changed, server returns 304 Not Modified with no body — saving bandwidth and improving perceived performance.
Conditional PUT: client includes the ETag in If-Match. If the resource has been modified since the client read it, the server returns 412 Precondition Failed — preventing the client from overwriting changes made by someone else.
ETags are essential for collaborative editing features and for mobile clients that must minimize data transfer. They are relatively inexpensive to implement (typically a hash of the response body or a last-modified timestamp) and produce significant benefits for frequently-read resources.
OpenAPI Specification and Documentation
Every production REST API should have an OpenAPI specification (formerly Swagger). OpenAPI describes every endpoint, request parameter, request body schema, response schema, authentication scheme, and error type in a machine-readable YAML or JSON format.
Benefits:
- Interactive documentation: Swagger UI and Redoc generate browsable documentation from the OpenAPI spec that developers can use to explore and test the API without writing code
- Client generation: tools like OpenAPI Generator produce client libraries in 40+ languages from the spec — clients get a type-safe, auto-completing API client without manual implementation
- Contract testing: use the spec to validate that the implementation matches the documented behavior
- API mocking: mock servers generated from the spec enable frontend development without requiring a running backend
Keep the OpenAPI spec in source control and regenerate it on every deployment. A spec that drifts from the implementation is worse than no spec — it actively misleads consumers.
Code-first approaches (NestJS with @nestjs/swagger, FastAPI with automatic schema generation) generate the OpenAPI spec from code annotations, eliminating the synchronization problem. For new projects, code-first generation is the recommended approach.
Summary: REST API Design Checklist
The decisions that matter most, applied consistently:
- Resource names: plural nouns, lowercase with hyphens, hierarchical path structure, two-level maximum nesting
- HTTP methods: GET/POST/PUT/PATCH/DELETE used according to semantics and idempotency properties
- Status codes: accurate 2xx/4xx/5xx codes including the correct 201/204 distinctions and 401 vs. 403 distinction
- Error responses: RFC 7807 format with type, title, status, detail, request_id, and field-level errors
- Pagination: cursor pagination for real-time data; offset for stable data; always include
has_moreand total count - Filtering and sorting: query parameters with documented filter fields;
sortwith-prefix convention - Idempotency keys: required for state-creating POST operations
- ETags: implement for frequently-read resources to enable conditional requests
- Versioning: URL path versioning (
/v1/,/v2/) with semantic versioning and formal deprecation policy - Documentation: OpenAPI specification generated from code, kept in sync with the implementation
Consistency across these ten dimensions produces an API that developers can understand, predict, and trust.
Related Articles
MLOps Guide: Taking Machine Learning Models to Production [2026]
87% of machine learning models built by data science teams never reach production. The models work — they pass cross-validation, they score well on holdout sets, they demonstrate genuine predictive value. The problem is not the modeling. The problem is everything that happens between a notebook experiment and a reliable, monitored, production system. MLOps is the discipline that closes that gap. This guide covers the full MLOps stack: maturity levels, tooling choices (MLflow, DVC, Kubeflow
Read More: MLOps Guide: Taking Machine Learning Models to Production [2026]LLM Fine-Tuning Guide: Custom Model Training with LoRA and QLoRA [2026]
General-purpose LLMs are impressive. They can write code, summarize documents, answer questions, and translate between languages with reasonable accuracy. But "reasonable" is not good enough when your application requires consistent output format, domain-specific terminology, a particular tone, or behavior that the base model was never trained to exhibit. That gap is where fine-tuning matters. Fine-tuning updates a model's weights on your specific data, changing how the model behaves — not
Read More: LLM Fine-Tuning Guide: Custom Model Training with LoRA and QLoRA [2026]Computer Vision Applications: Object Detection, OCR, and Industrial AI [2026]
Computer vision has moved well past the research phase. The models are trained, the frameworks are mature, the hardware is accessible, and the use cases are generating measurable returns. What was a specialized capability requiring deep expertise in 2018 is now deployable infrastructure — if you know which component to reach for and where the real complexity lives. This guide covers computer vision applications across industrial, medical, logistics, and document processing domains. It expl
Read More: Computer Vision Applications: Object Detection, OCR, and Industrial AI [2026]
