HTTP Status Codes in REST API Design
Choosing the right HTTP status code is part of your API contract. A well-chosen code tells the client exactly what happened, which party is responsible, and whether retrying will help. Returning 200 for every response and burying success or failure in a JSON body is a common anti-pattern that breaks HTTP-aware infrastructure: CDNs, load balancers, and API gateways all make decisions based on status codes.1 Consequently, misusing them adds hidden complexity. The codes exist to carry semantic meaning that generic fields in a JSON body cannot replicate. This guide covers the selection rules that matter most in REST API design: 2xx, 4xx, and 5xx, validation errors, and consistent error body conventions.
2xx: success codes and their differences
Inside the 2xx class, four codes dominate REST API design. 200 OK is the baseline for successful GET, PUT, and PATCH responses. 201 Created follows a POST or PUT that produces a new resource; include a Location header pointing to the new resource URI. 204 No Content is the right choice for a DELETE or a PUT that returns no body.
202 Accepted signals that the request was received but processing is asynchronous: the server has not yet completed the action. Returning 200 for an async request misleads the client into thinking the work is done. Returning the correct 2xx code communicates the action outcome precisely and lets clients branch logic correctly. When your team is deciding between 200, 201, and 204 for a specific endpoint, picking the right HTTP status code for an operation keeps the contract unambiguous for every client that integrates with it.
4xx: client error selection rules
Four decisions cover most 4xx cases and mastering them will resolve the majority of status code selection questions your team encounters when designing or reviewing REST API endpoints.2 Use 400 Bad Request for structurally broken requests: malformed JSON, wrong Content-Type, or a missing required field that prevents the request from being parsed. Use 422 Unprocessable Content for requests that parse correctly but fail semantic validation, such as an end date before a start date or a quantity below the configured minimum.
The 401 vs 403 split
The 401 vs 403 decision trips up many teams because the distinction is subtle but consequential for how clients respond to the error and whether they prompt the user to authenticate or inform them that access is denied. Return 401 when the request lacks credentials and you want the client to authenticate before retrying. Return 403 when credentials are present but the action is forbidden for that identity. Returning 404 instead of 403 is valid when you want to conceal that a resource exists, such as a private user profile, but document this convention in your API specification so clients do not mistake the 404 for a missing endpoint. Pick one convention and apply it consistently across your API surface.
Consistent error response bodies
The status code signals the class of error; the response body gives the detail. A minimal but consistent error body includes at least three fields: a machine-readable error code string (e.g., "validation_failed"), a human-readable message, and optionally an array of field-level errors for validation responses so the client can present each error next to the relevant input field.
Returning a different body structure for 400 vs 422 vs 500 forces clients to write three separate error-handling branches, which increases the size of every client codebase that integrates with your API and makes it harder to add new error types in the future. A single body schema for all 4xx and 5xx responses simplifies client code and API documentation. Many teams adopt RFC 7807 (Problem Details for HTTP APIs) as a standard schema: it defines type, title, status, detail, and instance fields that cover most error cases without inventing a custom format.3
Idempotency and status codes for PUT and DELETE
Idempotent HTTP methods produce the same resource state when called multiple times with the same input. PUT and DELETE are both idempotent by definition in RFC 9110.1 A PUT that creates a resource on first call and has no effect on subsequent calls with the same input should return 201 Created on first call and 200 OK (or 204 No Content) on subsequent calls. This distinction matters for clients that implement retry logic: a PUT that returns 201 twice signals a duplicate creation problem rather than idempotent behavior.
DELETE idempotency creates a common design question: what should a DELETE return when the resource does not exist? Two conventions exist. The first returns 404 Not Found because the client requested deletion of a resource that is not there. The second returns 204 No Content because the desired outcome (the resource not existing) is already achieved. RFC 9110 describes DELETE as idempotent but does not prescribe which code to return for a non-existent resource. Pick one convention and apply it consistently across all DELETE endpoints in your API.
Using 200 versus 204 after a successful PUT
A successful PUT can return either 200 OK with the updated representation in the body, or 204 No Content with an empty body. Return 200 when the server modifies the resource beyond what the client sent (adding timestamps, normalizing fields, or computing derived values), so the client can see the final state without a follow-up GET. Return 204 when the server applies the PUT body exactly as sent and the client does not need to see the updated representation.
Documenting status codes in OpenAPI 3.x
OpenAPI 3.x requires every route to document all possible response status codes under the responses object. Each listed code maps to a response schema that describes the body structure. Documenting all 4xx and 5xx codes your endpoint can return allows clients to generate accurate error-handling code and lets API testing tools verify that your implementation matches the specification.
The default response key covers all status codes not explicitly listed. A default entry with an error schema catches any 5xx or undocumented 4xx response without requiring you to list every possible code. Most OpenAPI tooling renders default as "Any other status code" in generated documentation. Use explicit codes for expected errors (400, 401, 403, 404, 422, 429) and default as a catch-all for unexpected failures.
The 422 response schema in OpenAPI
For endpoints that perform business rule validation, document the 422 response with a schema that matches your field-level error body. Include an errors array with field, code, and message properties in the 422 schema. When clients generate SDK code from your OpenAPI spec, the 422 schema drives the error type they use to surface field-level validation failures in their type system. A 422 response that returns a different body structure than what the OpenAPI spec describes breaks generated clients silently, without any immediate runtime error.
Validating your OpenAPI document against your actual API responses catches schema drift before it breaks client integrations. A contract test that sends known inputs to each endpoint and verifies the response body matches the documented 422 schema ensures your implementation stays aligned with the specification. This is especially valuable after framework upgrades or when you add new validation rules, because the generated SDK code will silently fail to parse the new error structure until a client reports the issue. Automated contract testing tools can compare your OpenAPI spec against live API responses and flag any mismatch, keeping your documentation trustworthy for every team that depends on it.
When to use this
Use this guide when designing a new REST API endpoint or auditing an existing one. Reference it when your team debates whether to return 400 vs 422, 401 vs 403, or 200 vs 204 for a specific operation, so the decision is grounded in HTTP semantics rather than preference.
Examples
POST /orders — successful creation
Return 201 Created with a Location header pointing to the new order resource. Do not return 200 OK for resource creation: 201 signals that a new URI was created.
DELETE /orders/123 — successful deletion
Return 204 No Content when the deletion succeeded and there is no body to return. Return 200 OK only if you include a body summarising the deleted resource.
POST /orders — semantic validation failure (end date before start date)
Return 422 Unprocessable Content with a field-level errors array. The request was valid JSON with all required fields present, so 400 Bad Request would be incorrect.
- 1.
R. Fielding, Ed., M. Nottingham, Ed., and J. Reschke, Ed., "HTTP Semantics," RFC 9110, IETF, June 2022. https://www.rfc-editor.org/rfc/rfc9110.txt
- 2.
M. Nottingham and R. Wilde, "Problem Details for HTTP APIs," RFC 7807, IETF, March 2016. https://www.rfc-editor.org/rfc/rfc7807.txt
- 3.
Mozilla Developer Network, "HTTP response status codes," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/HTTP/Status