REST API Design Guidelines and Best Practices
Design robust, consistent REST APIs with clear resource modeling, proper HTTP semantics, and future-proof versioning strategies.
REST API Design Guidelines and Best Practices
An API is a product. It should be intuitive, consistent, and easy to learn. This guide covers resource modeling, HTTP semantics, payload consistency, and versioningβthe fundamentals of APIs that scale.
π The Problem: Inconsistent API Design
Most APIs lack discipline. URLs mix conventions (camelCase vs kebab-case), response structures vary by endpoint, and error formats are inconsistent. Developers lose time reading docs, making mistakes, and debugging.
π οΈ Approach: API-First Design
Define your API contract before writing implementation code. This ensures consistency and forces you to think through design decisions upfront.
Resource Modeling
Think in terms of resources, not actions. Resources are nouns (users, orders, payments); actions are verbs (HTTP methods).
URL conventions:
| Rule | Example (Good) | Example (Bad) |
|---|---|---|
| Pluralize resources | /users | /user |
| Use kebab-case | /order-items | /orderItems |
| Keep URLs verb-free | /orders/{id}/items | /get-order-items |
| Use snake_case for query params | ?sort_by=created_at | ?sortBy=createdAt |
Resource relationship hierarchy:
/orders (collection of all orders)
/orders/{order_id} (specific order)
/orders/{order_id}/items (items in that order)
/orders/{order_id}/items/{item_id} (specific item)
π HTTP Methods and Status Codes
Use HTTP methods and status codes exactly as specified. This ensures compatibility with HTTP caching, proxies, and client libraries.
HTTP Methods
| Method | Purpose | Idempotent | Example |
|---|---|---|---|
| GET | Retrieve resource(s) | Yes | GET /orders |
| POST | Create new resource | No | POST /orders (with body) |
| PUT | Replace entire resource | Yes | PUT /orders/{id} |
| PATCH | Partial update | No | PATCH /orders/{id} |
| DELETE | Remove resource | Yes | DELETE /orders/{id} |
HTTP Status Codes
Success (2xx):
200 OKβ Request succeeded201 Createdβ Resource created by POST204 No Contentβ Successful deletion (no body to return)
Client Errors (4xx):
400 Bad Requestβ Invalid request syntax401 Unauthorizedβ Missing or invalid authentication403 Forbiddenβ Authenticated but not authorized404 Not Foundβ Resource doesnβt exist429 Too Many Requestsβ Rate limit exceeded (includeRetry-Afterheader)
Server Errors (5xx):
500 Internal Server Errorβ Unexpected server failure
π‘ Payload Consistency
Every response should follow the same structure. This reduces cognitive load for API consumers.
JSON conventions:
- Use
snake_casefor all property names - Use RFC 3339 format for timestamps:
2025-11-10T10:00:00Z - Avoid
nullfor boolean properties; omit the field entirely - Empty arrays should be
[], notnull
Example response:
{
"id": "ord-12345",
"customer_id": "cust-9876",
"status": "AWAITING_SHIPMENT",
"total_amount": {
"amount": 199.99,
"currency": "EUR"
},
"created_at": "2025-11-10T10:00:00Z",
"line_items": [
{
"sku": "product-a",
"quantity": 1
}
]
}
Error Response Format
Use Problem JSON (application/problem+json) for all errors:
{
"type": "https://example.com/problems/out-of-stock",
"title": "Not Enough Stock",
"status": 409,
"detail": "The requested quantity of item SKU-456 exceeds available stock.",
"instance": "/orders/req-123"
}
All error responses use this formatβno surprises.
π Idempotency and Safety
Idempotent operations return the same result regardless of how many times theyβre called. This is critical for handling network failures and retries.
- GET, PUT, DELETE are naturally idempotent
- POST is not (creates a new resource each time)
- Use an
Idempotency-Keyheader for safe POST retries
Example:
POST /payments HTTP/1.1
Idempotency-Key: acbd18db4cc2f85cedef654fccc4a4d9
Content-Type: application/json
{ "amount": 100, "currency": "USD" }
If the same key is used within 24 hours, the server returns the cached response instead of processing again.
π Versioning Strategy
Avoid URL versioning (/v2/orders). Itβs tightly coupled and forces all clients to upgrade simultaneously.
Use Media Type versioning instead:
GET /orders/{id} HTTP/1.1
Accept: application/x.myapi.order+json;version=2
Response:
HTTP/1.1 200 OK
Content-Type: application/x.myapi.order+json;version=2
This allows the server to support multiple versions concurrently and gives clients fine-grained control over which version they consume.
π Best Practices Summary
| Category | Recommendation |
|---|---|
| API Design | Define contract before code. Prioritize consistency over cleverness. |
| Resource Naming | Pluralize collections, use kebab-case, keep URLs verb-free |
| HTTP Semantics | Use correct methods and status codes; donβt invent your own |
| Responses | Consistent structure, snake_case properties, RFC 3339 timestamps |
| Errors | Problem JSON format for all errors; include actionable details |
| Idempotency | Use idempotency keys for safe retries on non-idempotent operations |
| Versioning | Media Type versioning for forward compatibility without URL coupling |
| Documentation | Auto-generate from OpenAPI/Swagger. Keep it in sync with code. |
| Testing | Test edge cases, error paths, and idempotency. Use contract testing. |
π‘ Key Takeaways
- Consistency is the primary metric: Developers will forgive a small limitation if itβs consistent.
- Follow HTTP semantics: Donβt reinvent the wheel. HTTP already solved these problems.
- Plan for versioning early: Media Type versioning beats URL versioning every time.
- Problem JSON prevents surprise errors: Standard error format means fewer integration bugs.
- Idempotency builds reliability: Clients can retry safely without worrying about side effects.