AlgoMaster Logo

Choosing the Correct HTTP Status Code

High Priority17 min readUpdated September 13, 2026
Listen to this chapter
Unlock Audio

When the server cannot complete a request, the HTTP status must communicate the failure to clients and infrastructure, even if they do not read the response body. Suppose a customer tries to cancel an order that has already shipped, and the API returns 200 OK with an error message in JSON. The application may notice the message, but a monitoring system counting HTTP failures records a success. A client library may also enter its success path before anyone reads the body.

An HTTP status code gives clients and infrastructure a shared description of a request's outcome. Choosing one requires understanding what the operation promised, why it could not proceed, and which protocol rules apply.

This chapter uses a fictional bookstore's JSON API over HTTPS to explain those decisions and the codes developers most often confuse.

1. Status Codes as Part of the Contract

A status code describes the outcome of the HTTP request. The response body can explain application-specific details, but it should agree with that outcome. For this API, a rejected cancellation uses an error status; a successful read of an order whose stored status is cancelled uses a success status.

The first digit identifies the response's broad class:

Scroll
ClassMeaningExample
1xxInterim information before a final response100 Continue permits a client to continue sending its request
2xxSuccessful handling, with each code defining its exact guarantee201 Created reports completed creation
3xxRedirection or conditional response handling304 Not Modified allows reuse of a stored representation
4xxThe server cannot fulfill the request because it detects a client-side problem403 Forbidden reports refusal to fulfill the request
5xxThe server failed to fulfill the request503 Service Unavailable reports temporary inability to handle it

These classes describe protocol outcomes, not blame. A valid client can encounter 409 because another customer bought the last available copy. A service can return 500 because its own code mishandled unusual input. Do not choose a status code based on which team wants responsibility for the incident.

Choose the family from the public outcome, then choose a specific code within that family. The diagram shows the main paths for ordinary JSON operations; interim responses and conditional reads have their own protocol handling.

The distinction between completed and accepted work matters as much as the distinction between success and failure. A 2xx response does not always mean every intended business effect has finished.

2. Successful Outcomes

Use the success code that matches what the server accomplished. The HTTP method contributes meaning, but does not select a single response code by itself.

Scroll
CodeAppropriate outcomeBookstore example
200 OKThe request succeeded; content describes the result according to the methodRetrieve a reading list or return its updated representation
201 CreatedThe request completed and created one or more resourcesCreate a reading list and identify it with Location
202 AcceptedThe server accepted the request for processing but has not finished itAccept a large catalog import for processing
204 No ContentThe request completed successfully and there is no response contentDelete a reading list without returning a representation

A POST does not automatically mean 201. An endpoint that calculates a delivery estimate can return 200 without creating an addressable resource. Conversely, a PUT that creates the target resource uses 201; PUT can also create resources.

Consider a completed reading-list creation:

The list exists when the API reports creation. For POST creation, HTTP recommends identifying the primary new resource with Location; this API always supplies it. The response representation is useful, but not every 201 response needs a JSON body.

All HTTP examples use the exact single-line bodies these examples show, with no trailing newline. Tokens are placeholders. Private responses use Cache-Control: no-store as an explicit API policy.

A 204 response cannot contain content or trailers, and the server must not send Content-Length, even with a value of zero. An empty JSON object is still content. For a completed deletion, this API can return:

For 202, explain the pending state and provide a way to inspect progress. Acceptance is not a guarantee of eventual success. If an import fails afterward, its status resource can record that failure. Retrieving that resource successfully can still return 200, because the GET succeeded even though the import did not.

Define success around the endpoint's promise. Creating an import-job resource may justify 201 if job creation is the promised result. Accepting an import that remains unfinished may justify 202. Document which operation the client is asking the server to perform.

3. Request Format and Validation

Several errors mean that the submitted request needs correction, but they identify different problems. Use that distinction to help clients decide what to change.

Scroll
CodeProblem the server reportsExample
400 Bad RequestA perceived client error, commonly malformed syntax or invalid request parametersBroken JSON or an invalid pagination parameter
413 Content Too LargeRequest content exceeds what the server acceptsA reading-list request larger than the endpoint's 8 KiB limit
414 URI Too LongThe request URI is longer than the server acceptsA query string containing thousands of IDs
415 Unsupported Media TypeThe request content's format or coding is unsupportedA client sends XML to an endpoint that accepts JSON only
422 Unprocessable ContentThe server understands the content type and the syntax is valid, but it cannot process the instructionsA reading-list name that is empty after trimming
431 Request Header Fields Too LargeOne header field or the combined header fields are too largeAn oversized set of cookies or custom headers

An API can choose how to distinguish 400 from 422, as long as it follows HTTP's rules. HTTP does not restrict 400 to JSON parsing errors. An API can consistently use it for field validation too. This bookstore uses 400 for malformed JSON and invalid query parameters, and 422 for well-formed JSON that violates its field rules.

For example, this body is valid JSON but fails the requirement for a nonblank name:

The error envelope and invalid_name value are application conventions. The HTTP code communicates the broad failure; the body identifies the particular correction. Neither a database exception name nor a framework's default mapping should decide this public contract accidentally.

Also distinguish request content from response negotiation. 415 concerns what the client sends. 406 Not Acceptable concerns whether the server's available response formats match the client's preferences, such as those in Accept. If a client requests XML and the endpoint can return only JSON, it can return 406 when it chooses to honor that restriction rather than supply a default representation. These codes describe opposite directions of the exchange.

4. Authentication, Authorization, and Concealment

Authentication establishes the caller's identity or credentials. Authorization determines whether the caller may make the request. Those failures need different responses.

Despite its name, 401 Unauthorized means the request lacks valid authentication credentials for the target resource. A server generating 401 must include WWW-Authenticate with an applicable challenge. For an expired bearer token:

For a request without credentials, this API sends a plain Bearer realm="bookstore" challenge without the invalid_token attribute. Obtaining valid credentials can address a 401; changing the requested resource's name cannot.

403 Forbidden means the server understands the request but refuses to fulfill it. In this API, a valid support token may read lists but cannot create them:

Signing in again with the same permissions does not fix this denial. Although a common use of 403 is to reject a caller who has valid credentials but insufficient permission, HTTP does not restrict the code to authenticated callers; other refusal policies can also produce it.

Sometimes even confirming that a resource exists discloses private information. HTTP permits 404 Not Found to conceal a forbidden resource. This bookstore uses a generic 404 for another customer's private list and for an absent list. Keep the body and cache policy consistent with that choice; returning 404 with a message saying who owns the hidden list defeats it.

This diagram shows the API's protected-resource policy after basic request handling:

This is an example disclosure policy, not a universal middleware order. An edge server may reject an oversized message before authentication. What matters is that error handling does not expose information the caller may not receive.

5. Missing Resources and Unsupported Methods

404 Not Found does not establish permanent removal. It means the server found no current representation for the target or is unwilling to disclose one. Use 410 Gone when access is no longer available and the server knows the condition is likely permanent, such as an intentionally retired public catalog export.

An empty collection is different. A valid GET /books?author=Unknown can return 200 with {"items":[]} because the query succeeded. An empty result does not make /books disappear. By contrast, a GET for a specific absent book normally returns 404 under this API's contract.

Deletion also needs an explicit policy. Repeating a successful DELETE can return 404 if the contract reports absence, or 204 if it promises to ensure absence without distinguishing earlier deletion. Different response codes on repeated calls do not by themselves violate idempotency; the intended effect on resource state is what matters.

Use 405 Method Not Allowed when the server recognizes a method but the target resource does not support it. A 405 response must include Allow listing that resource's currently supported methods. If the individual public book resource supports only GET, HEAD, and OPTIONS:

This differs from 501 Not Implemented, which reports functionality the server does not support, such as a method it does not recognize and cannot support for any resource. Do not use 501 merely because a caller attempted an unsupported operation on one endpoint.

HTTP caching rules allow caches to reuse some error responses, including 404, 405, and 410, using heuristic freshness. That means a cache may assign freshness without an explicit lifetime when the surrounding rules permit it. Choose cache controls deliberately, especially for temporary absence and permission-dependent responses.

6. Conflicts and Preconditions

A valid request can be incompatible with current resource state. 409 Conflict describes that situation. For this bookstore, cancelling an already shipped order conflicts with its allowed transitions. The order exists, the caller may cancel eligible orders, and the request is syntactically valid; its current state prevents this cancellation.

An application-level uniqueness conflict can also fit 409, such as creating a promotional code whose identifier is already in use. A repeated reading-list display name is not a conflict here because names are not unique. Select the status from the documented resource rules, not from the mere presence of similar database rows.

A precondition is a condition the client sends in an HTTP request header. The condition must hold before the server performs the action. For example, If-Match can require a representation to match a previously retrieved entity tag, an opaque value identifying that representation's version.

Use 412 Precondition Failed when an applicable request-header precondition fails and the server rejects the operation. Use 428 Precondition Required when the server requires a conditional request but the client omitted the required condition. These describe different corrections:

Scroll
SituationStatusWhat the caller needs to address
Order has shipped; caller can no longer cancel it409Resolve or accept the business-state restriction
Supplied If-Match tag does not match the current representation412Retrieve current state and reconsider the intended change
Endpoint requires If-Match, but it is absent428Supply the required condition based on retrieved state

Do not collapse every failed update into 409. The distinction tells the caller whether it omitted a required guard, supplied a guard that failed, or requested an action incompatible with business state. Caches must not store 428 responses.

Preconditions also depend on the method and header. A matching If-None-Match on a conditional GET can produce 304 Not Modified, while a failed If-None-Match: * condition on a creation request produces 412. Conditional reads are not application conflicts.

If multiple problems exist, avoid treating a decision table as a universal execution order. HTTP precondition evaluation generally follows normal request checks; supplying an entity tag does not make a request valid if it would already fail for another reason.

7. Rate Limits and Server Failures

429 Too Many Requests reports that the caller has sent too many requests within a period. 503 Service Unavailable reports temporary inability to handle the request, such as service overload or scheduled maintenance. Both may lead the caller to wait, but they describe different conditions.

A rate limit may apply to an account, token, or another documented grouping. General service overload can affect callers who have stayed within their limits. Do not label every capacity problem 429 simply because reducing traffic would help.

These server-error codes also need separate meanings:

Scroll
CodeMeaningExample
500 Internal Server ErrorAn unexpected server condition prevented fulfillmentA handler fails while constructing a valid order response
502 Bad GatewayA server acting as a gateway or proxy received an invalid upstream responseA gateway receives an invalid HTTP response from the order service
503 Service UnavailableThe service temporarily cannot handle the requestThe service rejects requests during maintenance
504 Gateway TimeoutA gateway or proxy did not receive a timely upstream responseThe order service does not respond within the gateway's deadline

Do not mechanically forward a dependency's status. If the bookstore's own credential for a shipping provider expires, the customer has not failed bookstore authentication. Returning the provider's 401 would tell the customer to fix credentials they do not control. Translate the failure according to the bookstore's role and public operation.

The diagram distinguishes an invalid upstream response from one that does not arrive in time:

These are alternative outcomes of a request, not two responses to the same client. A valid error response from an upstream application does not by itself justify 502.

A 429 or 503 response may include Retry-After. Its value is either an HTTP date or a nonnegative whole number of seconds, such as Retry-After: 60. Supply a useful estimate when available; it is guidance about waiting, not a guarantee that the next attempt will succeed. Caches must not store responses using 429.

Do not use 408 Request Timeout for slow database work. It means the server did not receive a complete request in the time it was prepared to wait. A client's local timeout may occur without any HTTP response at all.

Finally, a failure response does not always establish whether a write happened. An order may commit before a gateway emits 504. A status code alone cannot determine whether repeating a write is safe. Preserve that uncertainty instead of promising that every 5xx means nothing changed.

8. A Consistent Status-Code Policy

For each endpoint, define the promised success result and map expected failure conditions to a small, meaningful set of statuses. Align related endpoints so malformed JSON, missing credentials, unsupported methods, and state conflicts mean the same things across the API.

Use protocol-specific codes only for their defined purposes. 304 is for conditional GET or HEAD responses and carries no content; it is not a general response to an unchanged update. 206 Partial Content belongs to HTTP range responses, not ordinary pagination or a batch where only some items succeeded. A familiar name is not sufficient reason to reuse a code.

Keep the HTTP status and application error code separate. Several distinct field failures can share 422, and several business-state conflicts can share 409. Clients should use stable application codes for finer distinctions instead of parsing prose or requiring a unique HTTP code for every rule.

Review actual responses from both the application and its gateway. Required headers such as WWW-Authenticate and Allow must survive the full path to the client. Check that empty responses really contain no content, the API does not report pending work as completed, and private failures have an appropriate cache policy. Status selection is only correct when the rest of the response honors it.

The animation below shows how different request outcomes lead to different HTTP status codes.

Summary

Choose a status code from the outcome the endpoint promises and the reason the server accepted, completed, or rejected the operation. Distinguish invalid input, missing credentials, denied access, absent resources, state conflicts, failed preconditions, and server failures so callers can respond appropriately.

Use consistent application conventions within HTTP's rules. Include required headers, respect content and caching restrictions, and make the response body agree with the status. Acceptance and failure codes both have limits: do not present pending success or an uncertain write outcome as settled.