| name | http-rest-guidelines |
| description | Guidelines for designing and implementing HTTP REST APIs โ resource naming, HTTP semantics, status codes, error responses, pagination, and versioning |
REST Guidelines
Practical rules for designing and implementing REST APIs. These are protocol-level conventions โ they apply regardless of framework, language, or runtime.
Resource Design
Model resources as nouns, not verbs. The URL identifies the resource; the HTTP method expresses the action.
โ
GET /orders โ list orders
โ
POST /orders โ create an order
โ
GET /orders/42 โ get order 42
โ
PUT /orders/42 โ replace order 42
โ
PATCH /orders/42 โ partial-update order 42
โ
DELETE /orders/42 โ delete order 42
โ GET /getOrders
โ POST /createOrder
โ GET /orders/delete/42
URL Rules
Actions That Don't Map to CRUD
Use a sub-resource noun or a command endpoint for actions with no clear CRUD fit:
POST /orders/42/cancellation โ cancel order 42
POST /sessions โ log in (create a session)
POST /password-resets โ initiate a password reset
Never use verbs in the path for standard CRUD. Reserve POST sub-resources for state transitions or commands.
HTTP Methods
| Method | Safe | Idempotent | Typical use |
|---|
| GET | โ
| โ
| Read a resource |
| HEAD | โ
| โ
| Read headers only |
| OPTIONS | โ
| โ
| Discover allowed methods |
| POST | โ | โ | Create / trigger action |
| PUT | โ | โ
| Replace full resource |
| PATCH | โ | โ | Partial update |
| DELETE | โ | โ
| Delete a resource |
- Safe: no observable side effects. GET must never mutate state.
- Idempotent: calling N times produces the same result as calling once. Clients may safely retry idempotent methods on network failure.
- Use PUT only when the client sends the complete replacement document.
- Use PATCH for partial updates โ never send the full document for a partial change.
- Use DELETE for hard deletes. For soft deletes (archive), use PATCH or a sub-resource.
Status Codes
Return the most specific code that accurately describes the outcome.
2xx โ Success
| Code | Name | When to use |
|---|
| 200 | OK | Successful GET, PATCH, DELETE with response body |
| 201 | Created | Successful POST that created a resource |
| 202 | Accepted | Request accepted; processing is async |
| 204 | No Content | Successful DELETE or PUT/PATCH with no body |
- Always include
Location: /resources/{id} in 201 responses.
- Use
202 with a polling URL when processing may take time.
4xx โ Client Errors
| Code | Name | When to use |
|---|
| 400 | Bad Request | Malformed syntax, invalid body |
| 401 | Unauthorized | Missing or invalid credentials |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource does not exist (or must not be revealed) |
| 405 | Method Not Allowed | Verb not supported on this endpoint |
| 409 | Conflict | State conflict (duplicate, stale update) |
| 410 | Gone | Resource permanently deleted |
| 422 | Unprocessable | Syntactically valid but semantically invalid |
| 429 | Too Many Requests | Rate limit hit โ include Retry-After header |
5xx โ Server Errors
| Code | Name | When to use |
|---|
| 500 | Internal Server Error | Unexpected failure โ log details, never expose |
| 502 | Bad Gateway | Upstream service is down |
| 503 | Service Unavailable | Circuit open, overloaded โ include Retry-After |
Request Design
- Set
Content-Type: application/json on all requests with a body.
- Accept
application/json by default. Support content negotiation via Accept header when multiple formats are needed.
- Validate inputs at the API boundary โ reject early with
400 or 422.
- Never silently ignore unknown fields. Either reject them (
400) or strip them โ document which behavior applies.
Query Parameters
Use query parameters for filtering, sorting, pagination, and projection โ never for identity:
GET /products?category=shoes&sort=price:asc&fields=id,name,price
GET /orders?status=pending&created_after=2024-01-01
- Use
snake_case for query parameter names.
- Boolean flags as strings:
?include_archived=true, not ?include_archived=1.
Response Design
Every endpoint returns a consistent shape. Consumers should never need to special-case the structure per endpoint.
{ "data": { ... } }
{ "data": [ ... ], "pagination": { ... } }
Rules:
- Never return a naked array at the top level โ wrap in
data. This allows adding pagination, meta, or links later without breaking callers.
null is acceptable for optional fields on a resource, but never return null where a collection is expected โ return [].
- Use
camelCase for all JSON field names.
- Dates as ISO 8601 strings:
"2024-03-15T14:30:00Z". Never use Unix timestamps in REST responses.
- IDs as strings, not numbers โ JavaScript loses precision on integers > 2^53.
Error Responses
Follow RFC 9457 (Problem Details) for error responses. Use Content-Type: application/problem+json.
{
"type": "https://api.example.com/errors/validation-failed",
"title": "Validation Failed",
"status": 422,
"detail": "The request body contains invalid fields.",
"instance": "/orders",
"errors": [
{ "field": "email", "message": "Must be a valid email address" },
{ "field": "quantity", "message": "Must be greater than 0" }
]
}
type: a URI identifying the error class โ stable and documentable.
title: human-readable summary, stable per type.
status: mirrors the HTTP status code.
detail: instance-specific explanation for this request.
instance: the request path that triggered the error.
errors: optional โ include for field-level validation details.
Never expose stack traces, internal IDs, or database error messages in API responses.
Pagination
Default to cursor-based pagination for large or real-time datasets. Offset pagination is acceptable for small, stable datasets.
Cursor Pagination
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6MTAwfQ==",
"prevCursor": "eyJpZCI6ODF9",
"hasNext": true,
"hasPrev": true
}
}
GET /orders?cursor=eyJpZCI6MTAwfQ==&limit=25
- Cursors are opaque strings โ clients must not parse or construct them.
- Always include
hasNext/hasPrev to avoid an extra round-trip.
- Document the default and maximum
limit values.
Offset Pagination
{
"data": [...],
"pagination": {
"total": 340,
"limit": 25,
"offset": 50
}
}
- Suitable for paginated admin UIs where jumping to a specific page is needed.
- Not suited for real-time data โ items shift between pages as the dataset changes.
Versioning
Version the API whenever a breaking change is required.
URL Versioning (recommended)
/v1/orders
/v2/orders
Simple, visible, and cache-friendly. Clients migrate at their own pace while versions run in parallel.
Header Versioning (alternative)
Accept: application/vnd.example.v2+json
API-Version: 2024-03-01
Cleaner URLs but harder to test in a browser or via curl.
Rules
Authentication
- Use Bearer tokens via
Authorization: Bearer <token> โ never pass credentials in query strings.
- Use HTTPS everywhere. Never accept credentials over plain HTTP.
- Return
401 when credentials are missing or invalid.
- Return
403 when credentials are valid but the action is not permitted.
- Never distinguish between "user does not exist" and "wrong password" in error messages โ both are
401.
Common Mistakes
| Mistake | Fix |
|---|
Verbs in URLs (/getUser, /createOrder) | Use nouns + HTTP method to express intent |
Returning 200 for failed operations | Return the correct 4xx or 5xx code |
| Returning naked arrays at the top level | Wrap in { "data": [] } to allow future envelope additions |
| Using POST for all mutations | Use PUT, PATCH, DELETE per their defined semantics |
| Leaking internal errors in 500 responses | Log internally; return a generic message to the client |
| Deep URL nesting beyond two levels | Flatten to max two levels; use query params for further filtering |
| Inconsistent field naming (camelCase / snake_case) | camelCase in JSON bodies; snake_case in query parameters |
| Integer IDs in JSON | Use string IDs to avoid JavaScript precision loss |
| Unix timestamps | Use ISO 8601 โ human-readable and timezone-explicit |
No Location header on 201 responses | Always point to the newly created resource |
Cursor pagination without hasNext/hasPrev | Include them to save a round-trip |
| Silently removing fields in a non-breaking release | Deprecate first; remove only in a new major version |