The Decision Tree
Start here and follow the path that matches your situation.
Did the request succeed?
Yes, and I’m returning data → 200 OK
The default success response. Use for GET requests that return data, POST requests that perform actions, and PUT/PATCH requests that update and return the result.
Yes, and I created a new resource → 201 Created
Use after POST or PUT creates something new. Include a Location header with the URL of the new resource.
Yes, but there’s no body to return → 204 No Content
Use for successful DELETE requests, or PUT/PATCH when the client doesn’t need the updated resource back. The response has no body.
Yes, but I already did this → 200 OK or 204 No Content
For idempotent operations (PUT, DELETE) where the end state is the same regardless of how many times you call it, return the same success code you’d return on the first call.
Accepted, but not finished yet → 202 Accepted
Use when the server queued the request for async processing. The client should poll or receive a callback later. Good for long running jobs, batch operations, or webhook deliveries.
Does the client need to go somewhere else?
Permanently moved → 301 Moved Permanently
The resource has a new URL forever. Browsers and search engines update their records. Safe for GET requests. Note: some older clients change POST to GET on redirect.
Permanently moved, keep method → 308 Permanent Redirect
Like 301, but guarantees the HTTP method is preserved. A POST stays a POST. Use when method preservation matters (API redirects).
Temporarily moved → 302 Found
The resource is temporarily at a different URL. Browsers cache this for the session but check back. Most common temporary redirect in practice.
Temporarily moved, keep method → 307 Temporary Redirect
Like 302, but preserves the HTTP method. Use for temporary API redirects where a POST must stay a POST.
See other → 303 See Other
Use after a POST to redirect the client to a GET endpoint. Common pattern: POST /orders returns 303 with Location: /orders/123, then the client GETs the new order.
Is the problem on the client’s side?
Malformed request → 400 Bad Request
The request syntax is wrong. Invalid JSON, missing Content-Type, undecodable body. The client needs to fix the request before retrying.
Not authenticated → 401 Unauthorized
No credentials provided, or the credentials are expired/invalid. The client should authenticate (login, refresh token) and retry.
Authenticated but not allowed → 403 Forbidden
The server knows who the client is but they don’t have permission. Reauthenticating won’t help. Check roles/permissions.
Resource doesn’t exist → 404 Not Found
The URL doesn’t match any resource. Also commonly used to hide the existence of resources the client isn’t allowed to know about (instead of 403).
Wrong HTTP method → 405 Method Not Allowed
The endpoint exists but doesn’t support this method. Example: trying to DELETE on a read only endpoint. Include an Allow header listing valid methods.
Conflict with current state → 409 Conflict
The request conflicts with the resource’s current state. Common uses: duplicate creation attempts, optimistic locking failures (version mismatch), state machine violations (trying to publish an already published post).
Resource was deleted → 410 Gone
Like 404, but the server knows this resource used to exist and was intentionally removed. Stronger signal than 404 for search engines and caching.
Validation error → 422 Unprocessable Entity
The syntax is valid (it’s proper JSON) but the content is wrong. Missing required fields, wrong data types, values out of range. Use this for business logic validation failures.
Too many requests → 429 Too Many Requests
Rate limiting. Include a Retry-After header telling the client when to try again.
Is the problem on the server’s side?
Unexpected server error → 500 Internal Server Error
Something broke. Unhandled exception, null pointer, configuration error. The catch-all for “this is our fault.” Log the error, don’t expose internals to the client.
Not implemented → 501 Not Implemented
The server doesn’t support this functionality. Use for endpoints that are defined but not built yet, or for HTTP methods the server doesn’t recognize.
Upstream service failed → 502 Bad Gateway
The server is acting as a proxy/gateway and received an invalid response from the upstream service. Common with load balancers and reverse proxies when a backend crashes.
Service overloaded or in maintenance → 503 Service Unavailable
The server can’t handle the request right now. Could be overloaded, in maintenance, or starting up. Include a Retry-After header if you know when it’ll be back.
Upstream timeout → 504 Gateway Timeout
The server is acting as a proxy and the upstream didn’t respond in time. Different from 408 (client timeout): 504 means the server’s own upstream timed out.
Quick Reference Table
Success (2xx)
| Code | Name | When to use |
|---|---|---|
| 200 | OK | Default success, returning data |
| 201 | Created | New resource created (include Location header) |
| 202 | Accepted | Async processing started |
| 204 | No Content | Success, no body (DELETE, fire and forget PUT) |
Redirection (3xx)
| Code | Name | Preserves method? | Permanent? |
|---|---|---|---|
| 301 | Moved Permanently | No (may switch to GET) | Yes |
| 302 | Found | No (may switch to GET) | No |
| 303 | See Other | Always switches to GET | N/A |
| 307 | Temporary Redirect | Yes | No |
| 308 | Permanent Redirect | Yes | Yes |
Client Error (4xx)
| Code | Name | When to use |
|---|---|---|
| 400 | Bad Request | Malformed syntax |
| 401 | Unauthorized | Missing or invalid credentials |
| 403 | Forbidden | Authenticated but lacks permission |
| 404 | Not Found | Resource doesn’t exist |
| 405 | Method Not Allowed | Wrong HTTP method |
| 409 | Conflict | State conflict (duplicates, version mismatch) |
| 410 | Gone | Resource permanently removed |
| 422 | Unprocessable Entity | Valid syntax, invalid data |
| 429 | Too Many Requests | Rate limited |
Server Error (5xx)
| Code | Name | When to use |
|---|---|---|
| 500 | Internal Server Error | Unhandled server failure |
| 502 | Bad Gateway | Upstream returned invalid response |
| 503 | Service Unavailable | Overloaded or in maintenance |
| 504 | Gateway Timeout | Upstream didn’t respond in time |
Common API Patterns
REST CRUD operations
| Operation | Method | Success code | Error codes |
|---|---|---|---|
| List resources | GET | 200 | 401, 403 |
| Get one resource | GET | 200 | 401, 403, 404 |
| Create resource | POST | 201 | 400, 401, 403, 409, 422 |
| Full update | PUT | 200 or 204 | 400, 401, 403, 404, 422 |
| Partial update | PATCH | 200 or 204 | 400, 401, 403, 404, 422 |
| Delete resource | DELETE | 204 | 401, 403, 404 |
Headers you should include
| Status code | Recommended header |
|---|---|
| 201 | Location: /resource/id |
| 301, 302, 307, 308 | Location: https://new-url |
| 405 | Allow: GET, POST |
| 429 | Retry-After: 60 |
| 503 | Retry-After: 300 |