HTTP Status Code Best Practices
Guidelines for proper HTTP status code usage in REST APIs: when to use each code, common mistakes, and industry conventions.
Guidelines for proper HTTP status code usage in REST APIs: when to use each code, common mistakes, and industry conventions.
Practical guidance on using HTTP status codes in APIs: precision, async semantics, redirects, rate limiting with 429, cache-aware codes, consistent error formats and idempotency, each with a recommendation and a common mistake.
| Principle | Recommendation | Common mistake | Example |
|---|---|---|---|
| Use precise status codes | Choose the most specific code for each situation. 400 for bad syntax, 422 for semantic errors, 409 for conflicts. | Returning 400 for all client errors or 500 for all server errors. | POST /users with invalid email format -> 400 Bad Request. POST /users with duplicate email -> 409 Conflict. |
| Never expose internal details in error responses | Return generic messages to clients; log detailed errors server-side. Use structured error bodies (RFC 7807 Problem Details). | Returning stack traces or SQL errors in production responses. | {"type":"https://api.example.com/errors/rate-limit","title":"Too Many Requests","status":429,"detail":"Please wait before retrying."} |
| Use 2xx correctly for async operations | 202 Accepted for async processing, 200 OK for immediate results, 201 Created for new resources. | Always returning 200 for async endpoints even when resource not yet available. | POST /jobs/submit -> 202 Accepted with Location header pointing to job status endpoint. |
| Redirect semantics matter for SEO | 301 for permanent moves (search engines transfer ranking), 302 for temporary, 307/308 to preserve HTTP method. | Using 302 for permanent URL changes, losing SEO link equity. | Old URL permanently moved -> 301 Moved Permanently with Location header. Feature temporarily disabled -> 307 Temporary Redirect. |
| Rate limiting: use 429 with Retry-After | Always include Retry-After header with 429 responses. Use informative error body with rate limit context. | Returning 503 for rate limiting (reserved for server overload) or omitting Retry-After. | HTTP/1.1 429 Too Many Requests Retry-After: 120 X-RateLimit-Limit: 100 X-RateLimit-Remaining: 0 |
| Cache-aware status codes | Use 304 Not Modified with ETag/Last-Modified for conditional requests. Set Cache-Control and Expires headers on 200 responses. | Returning full response body for unchanged resources or omitting cache validation headers. | GET /resource with If-None-Match: "etag123" -> 304 Not Modified (empty body, cached version still valid). |
| Consistent error response structure | Use a consistent JSON envelope for all errors across your API. Include error code, message, and optional details array. | Inconsistent error formats between endpoints, making client error handling impossible. | {"error":{"code":"VALIDATION_ERROR","message":"Invalid input","details":[{"field":"email","reason":"already_taken"}]}} |
| Idempotency and safe methods | GET, HEAD, OPTIONS, DELETE, PUT should be idempotent. POST and PATCH are not. Return 200 on successful DELETE even if resource existed. | Returning 404 on idempotent DELETE (idempotent means same response for same request, 200 for first delete, 200 for subsequent). | DELETE /users/123 (first time) -> 200 OK. DELETE /users/123 (again) -> 200 OK (idempotent, not 404). |
The shape of this dataset, read from the data itself: every column, its type and a real value. Agents can use this to plan a query without fetching the whole document.
| Section | Field | Type | Example |
|---|---|---|---|
| practices | principle | string | Use precise status codes |
| practices | recommendation | string | Choose the most specific code for each situatio… |
| practices | common_mistake | string | Returning 400 for all client errors or 500 for … |
| practices | example | string | POST /users with invalid email format -> 400 Ba… |
The static files contain the same reference rows as the API, with no API rate limit. Live metadata, such as view counts, may differ. Use the API for content negotiation; use the static files for a cacheable snapshot.
curl "https://yjtoon.yunusemrevurgun.com/api/dataset/http-status-code-best-practices?format=toon"
const res = await fetch( "https://yjtoon.yunusemrevurgun.com/static-data/dataset/http-status-code-best-practices.toon" ); const toon = await res.text();
Rate limit: 120 requests per minute per IP, no key and no signup. API reference →