Overview
Pick the status code by intent: the class (2xx, 3xx, 4xx, 5xx) tells the client what to do next and the code adds the reason. Names follow RFC 9110. Redirect handling for search is in redirects; framework error handling is in fastapi.
Success and redirects
Use the most specific success code, and 307 or 308 when the method must be kept.
| Code | Use for |
|---|---|
| 200 OK | Success with a body. |
| 201 Created | New resource; send a Location header. |
| 202 Accepted | Async job queued; Location points to a status URL. |
| 204 No Content | Success with no body (DELETE, or a PUT that does not echo). |
| 206 Partial Content | Range request honored. |
| 301 / 308 | Permanent move. 308 keeps the method; clients may rewrite POST to GET on 301. |
| 302 / 307 | Temporary move. 307 keeps the method; avoid 302 for non-GET. |
| 303 See Other | After a POST, GET another URL (Post-Redirect-Get). |
| 304 Not Modified | Cached copy valid after If-None-Match or If-Modified-Since. |
Client errors
Be specific; clients debug from the code.
| Code | Use for |
|---|---|
| 400 Bad Request | Unparseable request or missing required header or query param. |
| 401 Unauthorized | Not authenticated; send WWW-Authenticate. |
| 403 Forbidden | Authenticated but not allowed. |
| 404 Not Found | Unknown resource or path. |
| 405 Method Not Allowed | Wrong verb; send Allow. |
| 408 Request Timeout | Client was too slow to send the request. |
| 409 Conflict | Duplicate insert or version conflict. |
| 410 Gone | Removed on purpose; caches and crawlers stop retrying. |
| 412 / 428 | If-Match precondition failed / required (optimistic locking). |
| 413 Content Too Large | Body over the limit. |
| 414 URI Too Long | Move the data to a body. |
| 415 Unsupported Media Type | Content-Type not handled. |
| 422 Unprocessable Content | Body parsed but failed validation. |
| 429 Too Many Requests | Rate limit; send Retry-After. |
| 431 | Request headers too large. |
| 451 | Blocked for legal reasons. |
Server errors
Return 5xx only for failures the client cannot fix; they should alert someone.
| Code | Use for |
|---|---|
| 500 Internal Server Error | Unhandled exception. |
| 501 Not Implemented | Method not supported at all. |
| 502 Bad Gateway | Upstream returned an invalid response. |
| 503 Service Unavailable | Overload or maintenance; send Retry-After. |
| 504 Gateway Timeout | Upstream did not answer in time. |
Pick the status by action
Map each handler action to one status and one companion header.
| Action | Status |
|---|---|
| Created a resource | 201 + Location. |
| Updated; no body / body returned | 204 / 200. |
| Deleted | 204. |
| Validation failed on a JSON body | 422 with field errors. |
| Not logged in / lacks permission | 401 / 403. |
| Resource missing / retired | 404 / 410. |
| Duplicate or version conflict | 409. |
| Rate limited / maintenance window | 429 or 503, with Retry-After. |
| Async job accepted | 202 + Location. |
Error body
RFC 9457 (Problem Details) defines application/problem+json. Keep status equal to the HTTP status and add extension members such as an errors array.
{
"type": "https://example.com/errors/validation",
"title": "Validation failed",
"status": 422,
"detail": "email must be a valid address",
"instance": "/users"
}Gotchas
Avoid these; each misleads clients, caches, or monitoring.
| Gotcha | Fix |
|---|---|
200 with {"error": ...} hides failures from clients, caches, and monitoring. | Return the real 4xx or 5xx. |
401 means unauthenticated, despite its name. | Use 403 for a known user without permission. |
| Browsers and many clients turn POST into GET after a 301 or 302. | Use 303 for Post-Redirect-Get, 307 or 308 to keep the method. |
Omitting Retry-After on 429 or 503 forces blind retries. | Always send it. |
| Stack traces in error bodies leak internals. | Log them; return an id in instance. |