HTTP Resource Semantics and Status Codes
Choose methods for the requested operation, status codes for the result, and representations for the data or error details. Clients, caches, proxies, and operators depend on that contract. A JSON field called success cannot replace the semantics of the HTTP response carrying it.
Start with a few observable cases for each resource: read, missing resource, invalid input, denied action, conflicting update, and temporary failure. Decide what each caller should do next, then make the response support that behavior.
TL;DR
- Use safe methods for retrieval; distinguish safety from idempotency and from identical response bodies.
- Choose success codes that distinguish a completed result, a newly created resource, and accepted asynchronous work.
- Keep error status, media type, and structured problem details consistent.
- Define cache policy explicitly, especially for sensitive or personalized responses.
- Use supported conditional-request semantics when protecting updates from stale clients.
- Test methods, headers, empty bodies, and errors rather than checking only one successful JSON response.
Quick Example
This complete Node.js ES module exports a small, in-memory handler using standard Request and Response objects. Save it as items.mjs. It performs no network request, authentication, persistence, or framework routing. Import handle in a test and call it with a constructed Request.
For the known resource, GET returns a representation and HEAD returns no body. An unsupported method returns 405 with Allow. Errors use the Problem Details media type defined by RFC 9457. about:blank uses the HTTP status meaning; this example defines no custom problem type.
The handler deliberately excludes caching and writes so their semantics are not hidden inside a tiny demo. A production framework also needs to handle transport, routing, validation, access, and any required header policy.
Core Concepts
Safety and idempotency answer different questions
Safe methods request read-oriented behavior. Idempotency means the intended server effect of repeating the same request matches a single request; it does not promise identical status codes or bodies. RFC 9110 defines these properties and conditional-request semantics.
For example, a deletion might succeed and a later repetition find nothing left. That can preserve the intended effect while returning different responses. By contrast, blindly repeating a create command may create another object unless the application defines duplicate handling.
Status codes describe the result
Use MDN's status reference to check the exact meaning and required headers. A useful starting map is:
The map is not an exhaustive API design. Define input and authorization errors according to their actual meaning, including required authentication challenges where applicable. Avoid exposing protected resource existence through inconsistent errors.
Error bodies complement status
Problem Details provides a common structure for machine-readable errors. Keep a stable problem identifier for application-specific categories, human-readable explanations appropriate to the recipient, and safe diagnostic correlation. Do not expose stack traces, credentials, internal queries, or another tenant's data in an error response.
Clients should not parse a translated prose sentence to choose their recovery action. Document stable identifiers and field conventions, and keep the response's status aligned with its body.
Define caching and concurrent updates
RFC 9111 distinguishes cache directives. In particular, no-cache requires successful validation before reuse; no-store prohibits storing the exchange under its specified rules. They are not interchangeable spellings for “disable everything.” Choose the policy from the representation's sensitivity and freshness requirements.
For public content, validators such as ETags can help a client revalidate an existing representation. A 304 response is not a new JSON representation. Test conditional requests through the intended proxy and client, including metadata and cache variation.
For updates, define how a caller proves it is acting on the state it previously read. A supported If-Match workflow can reject a stale update with a failed precondition rather than silently overwriting another writer's change. The server must enforce the condition at the relevant update boundary; checking it in browser code alone leaves a race.
Keep cache policy separate from authorization. A private cache directive does not decide whether a user may read a record, and an authenticated request does not automatically make its response safe for a shared cache. Review the complete response path.
Best Practices
Specify the next caller action
For each important response, document whether the client should show data, correct input, refresh state, authenticate, wait, or stop retrying. The contract becomes more useful when it supports an action rather than just naming an error.
Test the complete response
Check status, headers, media type, and body rules. Include HEAD, unsupported methods, and an empty response where applicable. A successful JSON parser is not a full HTTP test.
Keep retries tied to operation meaning
Review whether a request may already have taken effect when a response is lost. Use deliberate duplicate handling for business operations that need it. A timeout does not establish that nothing happened.
Common Mistakes
Returning 200 for every outcome
Bad: Put every failure into a success-status envelope and make each client rediscover the result.
Correct: Use meaningful HTTP status and structured error details together. Preserve the conventions clients and intermediaries expect.
Returning content with a no-content status
Bad: Send a JSON confirmation body with 204 and expect clients to consume it.
Correct: Choose a status/body contract that matches the result, and verify it through the actual HTTP stack.
Treating browser validation as concurrency control
Bad: Compare a version in the UI and then perform an unconditional write.
Correct: Enforce the documented precondition at the server update boundary and return an appropriate failure when it no longer holds.
FAQ
Is DELETE idempotent if the second call returns 404?
Idempotency concerns intended effect, not identical responses. Document how the API represents an already-absent resource and preserve authorization behavior.
Are 401 and 403 interchangeable?
No. Use their documented semantics and required authentication headers. Also decide how to avoid revealing protected resource existence; a generic status rule cannot replace that review.
Does no-cache mean nothing is stored?
No. It restricts reuse without validation. no-store has different storage semantics. Check the applicable cache and client behavior for sensitive data.
Should error messages include debugging details?
Include details safe for the recipient and useful for recovery. Keep sensitive internals in appropriately protected diagnostics with a correlation mechanism.
Related Topics
- REST API Design — Resource contracts and API evolution.
- HTTP — Protocol semantics and request handling.
- API Security — Authorization and safe error behavior.
- gRPC vs REST — Translate contracts deliberately across interfaces.