Choosing the right HTTP status code makes an API easier to integrate, test, monitor, and maintain. This guide explains what common responses mean, how to distinguish similar codes such as 400 and 422 or 401 and 403, how to design consistent REST API error responses, and how to debug client failures using status codes, headers, and response bodies.
Overview
An HTTP status code is a compact signal about the result of a request. It does not replace a useful response body, but it gives clients and operators an immediate way to classify the outcome. A successful response may contain the requested resource, a redirect may tell a client to use another location, a client error may require changes to the request, and a server error may indicate that the operation should be investigated or retried later.
For API design, consistency matters more than using every available code. Define a small, documented set that accurately represents your API's behavior. A client should be able to decide whether to display a validation message, refresh credentials, resolve a conflict, follow a redirect, or retry without parsing arbitrary text.
Use the first digit as a quick classification:
- 2xx: The request was received and handled successfully.
- 3xx: The client may need to use another location or take an additional step.
- 4xx: The request cannot be fulfilled as submitted, or the client lacks permission.
- 5xx: The server or an upstream dependency could not complete a seemingly valid request.
Do not use a status code merely because its number sounds close to the problem. A precise code becomes part of your API contract, so changing it can affect client logic, alerting, automated tests, and retry behavior.
What to track
HTTP status code reference
| Code | Typical meaning | Common API use |
|---|---|---|
| 200 OK | The operation completed successfully. | Returning a representation after a read or update. |
| 201 Created | A new resource was created. | Successful POST that creates a resource, often with a Location header. |
| 202 Accepted | The request was accepted for processing, but work is not complete. | Queued or asynchronous operations. |
| 204 No Content | The operation succeeded without a response body. | Deletion or an update where no representation is returned. |
| 301/308 | The resource has a permanent alternate location. | Infrastructure or endpoint migration; choose redirects deliberately for API clients. |
| 304 Not Modified | The cached representation remains valid. | Conditional requests using validators such as ETag. |
| 400 Bad Request | The request is malformed or cannot be parsed as required. | Invalid JSON syntax, malformed query parameters, or an unusable request structure. |
| 401 Unauthorized | Authentication is missing or not accepted. | Missing, expired, malformed, or invalid credentials. |
| 403 Forbidden | The server understands the request but will not authorize it. | Insufficient permissions or a policy restriction. |
| 404 Not Found | No current representation is available at the requested target. | Unknown route or resource that is not exposed to the caller. |
| 405 Method Not Allowed | The method is not supported for that resource. | Using POST where an endpoint supports only GET and DELETE. |
| 409 Conflict | The request conflicts with the current state of the resource. | Duplicate creation, version conflict, or a state transition that cannot occur. |
| 410 Gone | The resource is intentionally no longer available. | A retired endpoint or resource whose removal is known and permanent. |
| 412 Precondition Failed | A request condition was not satisfied. | An If-Match or similar precondition does not match the current resource. |
| 415 Unsupported Media Type | The payload format is not supported. | Sending an unsupported Content-Type. |
| 422 Unprocessable Content | The content is understood, but semantic validation fails. | Well-formed JSON with invalid field values or business validation errors. |
| 429 Too Many Requests | The client has sent too many requests in a period. | Rate limiting, ideally with useful retry guidance. |
| 500 Internal Server Error | The server encountered an unexpected condition. | An unhandled application failure; do not expose sensitive diagnostics. |
| 502 Bad Gateway | A gateway received an invalid response from an upstream service. | Proxy or service-to-service integration failure. |
| 503 Service Unavailable | The service cannot handle the request now. | Temporary overload, maintenance, or unavailable dependency. |
| 504 Gateway Timeout | An upstream service did not respond in time. | Timeout between a gateway and a dependency. |
400 vs. 422
Use 400 when the server cannot properly interpret the request. Examples include broken JSON, an invalid request-line structure, or a parameter format that prevents parsing. Use 422 when the request is structurally understandable but fails semantic validation. For example, a valid JSON document with an end date earlier than its start date can reasonably produce 422. Teams may standardize on one validation code, but the distinction should be documented and applied consistently.
401 vs. 403
401 concerns authentication: the client has not demonstrated an acceptable identity. The response can indicate how authentication may be provided, while avoiding disclosure of sensitive details. 403 concerns authorization: the request is understood, but the authenticated caller is not allowed to perform it. Do not return 401 simply because a user lacks a role.
404 vs. 410
Return 404 when the target cannot be found or the API intentionally does not reveal whether it exists. Use 410 when the server knows that the resource or endpoint has been deliberately removed and wants clients to treat that removal as lasting. If resource existence is sensitive, a uniform 404 response can also reduce information disclosure.
409 vs. 412
A 409 describes a conflict with the resource's current state, such as attempting to create a duplicate username or applying an invalid state transition. A 412 is specifically about a failed request precondition, commonly an optimistic concurrency check such as If-Match. In both cases, give the client enough information to resolve the problem without exposing internal data.
Cadence and checkpoints
Designing response codes is not a one-time exercise. Track how your API actually uses them and review the contract on a regular cadence. A monthly or quarterly review is practical for active APIs; less frequently changing services can review during each planned release. The goal is not to force every endpoint into a new pattern, but to detect drift before it becomes an integration problem.
Response-design workflow
- Describe the operation. Record the method, target resource, authentication requirements, and whether the operation is synchronous or asynchronous.
- List normal outcomes. Choose the success code, response body, location information, caching behavior, and relevant headers.
- List request failures. Separate malformed input, semantic validation, authentication, authorization, missing resources, conflicts, and unsupported methods.
- List temporary failures. Decide which dependency, timeout, overload, or rate-limit conditions may be retried and which should fail immediately.
- Define the error shape. Use stable machine-readable fields and keep human-readable messages safe to show or log.
- Test the contract. Add automated tests for each documented code, required header, body field, and retry rule.
For each response, track the status code, endpoint, method, request correlation ID, latency, dependency information where appropriate, and a sanitized error category. Avoid logging access tokens, passwords, payment data, or complete user-submitted payloads by default.
Copy-ready REST error response
{
"type": "https://api.example.com/problems/validation-error",
"title": "Request validation failed",
"status": 422,
"detail": "One or more fields are invalid.",
"instance": "/orders/123",
"requestId": "req_abc123",
"errors": [
{ "field": "quantity", "code": "must_be_positive" }
]
}
The exact field names are a team decision. What matters is that clients can depend on the status and stable error codes, while people can understand the title and detail. Keep internal stack traces and database messages on the server side. For a temporary failure, a simpler response can include a retry hint:
{
"type": "https://api.example.com/problems/temporary-failure",
"title": "Service temporarily unavailable",
"status": 503,
"detail": "Try the request again later.",
"requestId": "req_abc123"
}
Use headers intentionally. A Location header is useful after creation or relocation. An ETag supports conditional updates. Retry-After can communicate a server-provided delay when applicable, but clients should still apply bounded backoff and a maximum attempt count.
How to interpret changes
A status code is a starting point for diagnosis, not a complete explanation. When a client failure appears, first capture the method, URL, status, response headers, response body, request ID, and a timestamp. Then compare the request with the API contract: check the Content-Type, authorization header, query encoding, path parameters, conditional headers, and payload shape.
Client debugging checklist
- Confirm that the request reached the expected host, environment, and route.
- Check whether the method is supported and whether a redirect changed the method or removed credentials.
- Inspect Content-Type and Accept headers, then verify that the body matches the declared format.
- For 400 or 422, read field-level errors and reproduce the smallest failing request.
- For 401, verify credential presence, expiration, audience, scope, and server clock assumptions without logging the secret itself.
- For 403, check the caller's role, resource ownership, tenant boundary, and policy conditions.
- For 404, verify identifiers, URL encoding, environment, and whether the API intentionally hides inaccessible resources.
- For 409 or 412, reload the current resource and inspect version or ETag handling before retrying an update.
- For 429, 502, 503, or 504, inspect rate-limit and retry headers, dependency health, timeout settings, and request idempotency.
- For 5xx responses, use the request ID to correlate client logs with server logs and traces.
Retry only when the failure is plausibly temporary and the operation is safe to repeat. GET, HEAD, PUT, and DELETE are commonly designed to be idempotent, but implementation details still matter. A POST that creates a resource may create duplicates if retried after an ambiguous timeout. For such operations, support an idempotency key where appropriate, store the key and result according to the API contract, and ensure concurrent duplicate requests cannot create multiple records.
Use exponential backoff with jitter, a bounded number of attempts, and an overall time limit. Do not retry validation, authentication, authorization, or most not-found responses without changing the request. A retry policy belongs in the client contract and should be tested with simulated timeouts, connection resets, 429 responses, and partial upstream failures.
When to revisit
Revisit this status-code guide and each API contract on a monthly or quarterly cadence, and immediately when a framework, gateway, authentication layer, proxy, or upstream service changes its defaults. Framework upgrades can affect validation responses, exception mapping, redirect behavior, and generated documentation. Infrastructure changes can also introduce new 502, 503, or 504 paths that were not present in local testing.
At each checkpoint, review:
- Unexpected increases in 4xx or 5xx responses by endpoint and client version.
- Whether documented status codes match production behavior and automated tests.
- Whether error bodies remain stable, safe, localized as needed, and useful to clients.
- Whether retry logic is causing duplicate writes, traffic spikes, or slow user experiences.
- Whether rate limits, timeout values, cache validators, and idempotency rules still match operational needs.
- Whether retired routes should remain 404 or be communicated as 410.
Finish the review by updating the API specification, contract tests, client SDK guidance, dashboards, and runbooks together. For hands-on diagnosis, pair this article with the API Debugging Runbook. If an endpoint depends on data queries, the SQL Query Formatting and Review Guide can help separate an API-layer failure from a database-layer failure.
As a final action, choose one endpoint and write down every expected success, client-error, and server-error path. Assign a status code, response shape, retry rule, and test for each path. Repeating that exercise during release reviews keeps HTTP behavior predictable and gives future clients a contract they can safely build against.