Real‑world examples of best practices for HTTP status codes in APIs
Examples of best practices for HTTP status codes in APIs
The fastest way to understand HTTP status codes is to see how they behave in real APIs. Instead of memorizing every code, focus on consistent patterns. The best examples share three traits:
- The status code tells you at a glance which class of outcome occurred (success, client error, server error).
- The response body gives structured, machine‑readable details.
- The same scenario always maps to the same code.
Below are concrete examples of best practices for HTTP status codes in APIs that you can adopt or adapt for your own services.
Success responses: go beyond 200 OK
Many APIs overuse 200 OK, but the best examples treat 2xx codes as a vocabulary, not a single word.
Example of creating, reading, updating, deleting
For a typical REST‑style resource (/users), a clean pattern looks like this:
- Create:
POST /users→201 Created- Include a
Locationheader pointing to the new resource:Location: /users/123. - Response body: the newly created user object.
- Include a
- Read:
GET /users/123→200 OK- Response body: current representation of the user.
- Update (PUT/PATCH):
PATCH /users/123→200 OKor204 No Content- Use
200if you return the updated resource. - Use
204if you return no body.
- Use
- Delete:
DELETE /users/123→204 No Content- No body, just confirmation via status code.
This is one of the best examples of predictable behavior: clients can infer what happened without parsing the body first.
Example of asynchronous operations
For long‑running jobs (video processing, large imports), synchronous 200 OK doesn’t tell the whole story. A better pattern:
POST /videos→202 Accepted- Body contains a job ID and status URL:
{ "job_id": "abc123", "status_url": "/jobs/abc123" }.
- Body contains a job ID and status URL:
GET /jobs/abc123→200 OKwith{ "status": "pending" }or{ "status": "completed" }.303 See Otherredirecting to the final resource when complete is another valid option.
This pattern matches the HTTP spec and appears in several public APIs as one of the best examples of handling async workflows.
Client errors: use 4xx codes to teach, not punish
The 4xx range is where many APIs get messy. The strongest examples of best practices for HTTP status codes in APIs make 4xx codes highly informative.
Example of structured validation errors (400 Bad Request)
When input is malformed or fails validation, use 400 Bad Request with a structured error payload. A practical pattern:
{
"error": {
"type": "validation_error",
"message": "One or more fields are invalid.",
"fields": [
{
"field": "email",
"code": "invalid_format",
"message": "Email must be a valid address."
},
{
"field": "age",
"code": "min_value",
"message": "Age must be at least 18."
}
]
}
}
Every invalid request gets 400, but the body explains the why in a consistent schema. This is a best example of how to make your API self‑documenting.
Example of authentication vs. authorization (401 vs 403)
Many APIs blur the line between 401 and 403. The cleaner pattern:
401 Unauthorized(really “Unauthenticated”):- Missing or invalid credentials.
- Include a
WWW-Authenticateheader when using standards like OAuth 2.0.
403 Forbidden:- Credentials are valid, but the user is not allowed to perform the action.
Real example:
- Missing token:
GET /admin/users→401 Unauthorized- Body:
{ "error": { "type": "auth_error", "message": "Missing access token." } }
- Body:
- Insufficient role:
GET /admin/userswith a basic user token →403 Forbidden- Body:
{ "error": { "type": "authorization_error", "message": "Admin role required." } }
- Body:
Clear separation here is one of the best examples of designing APIs that are easy to debug in production.
Example of resource not found (404 Not Found)
Use 404 Not Found when the resource does not exist or when you intentionally want to hide whether it exists (for privacy/security).
Pattern:
GET /users/999999(never existed) →404 Not Found.GET /users/email/jane@example.com(user exists but caller not allowed) → still404 Not Foundwith a generic message.
The same code for both cases avoids leaking information about resource existence.
Example of method and media type errors (405 and 415)
Two underused but helpful codes:
405 Method Not Allowed: Method is invalid for this resource.PUT /sessions/currentwhen onlyGETis supported.- Include an
Allowheader:Allow: GET, DELETE.
415 Unsupported Media Type: Payload type not supported.POST /userswithContent-Type: text/plainwhen onlyapplication/jsonis accepted.
These codes give API consumers precise feedback instead of a vague 400.
Server errors: treat 5xx as incidents, not normal flow
5xx codes mean your system failed, not the client. The best examples of best practices for HTTP status codes in APIs treat any 5xx as a signal to investigate.
Example of internal errors (500 Internal Server Error)
Use 500 for unexpected conditions only:
- Null pointer, unhandled exception, misconfigured dependency.
- Downstream service failure not mapped to a more specific code.
A good practice is to return a stable error shape while logging details internally:
{
"error": {
"type": "server_error",
"message": "An unexpected error occurred. Try again later.",
"request_id": "req_9f8a7c"
}
}
Clients can log request_id and you can trace it in your logs.
Example of unavailable vs. timeout (503 vs 504)
When dependencies fail, more specific codes help operations teams:
503 Service Unavailable: Your service is overloaded or in maintenance.- Include
Retry-Afterheader when you expect recovery:Retry-After: 120.
- Include
504 Gateway Timeout: Your service acted as a gateway/proxy and a downstream service timed out.
In distributed systems, using 503 and 504 correctly feeds better metrics into API gateways and load balancers.
Pagination, rate limits, and idempotency: modern patterns
Modern APIs share some consistent patterns that are now considered best examples of thoughtful HTTP status code usage.
Example of rate limiting (429 Too Many Requests)
Instead of a generic 403, use 429 Too Many Requests when clients exceed rate limits. A practical response:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1710001234
Body:
{
"error": {
"type": "rate_limit_exceeded",
"message": "You have exceeded the limit of 100 requests per minute."
}
}
This is one of the best examples of best practices for HTTP status codes in APIs because it combines:
- A specific status code (
429). - Clear retry guidance (
Retry-After). - Machine‑readable rate limit metadata.
Example of idempotent writes (409 Conflict and 422 Unprocessable Entity)
For idempotent operations (like creating resources with a client‑supplied idempotency key or natural key), you can use:
409 Conflictwhen a resource already exists in a way that conflicts with the request.POST /userswithemailthat already exists.
422 Unprocessable Entitywhen the payload is syntactically valid JSON but semantically invalid.POST /transferswhereamountis greater than the account balance.
Real example pattern:
POST /userswith duplicate email →409 Conflict.POST /transferswith amount-10→422 Unprocessable Entity.
These distinctions give client developers clear signals about whether they can safely retry or need to change the request.
Consistent error schemas: pairing codes with bodies
Status codes tell you what class of outcome you’re dealing with; error bodies explain the details. The best examples of best practices for HTTP status codes in APIs always pair codes with a consistent JSON schema.
A widely used pattern (inspired by APIs like Stripe and GitHub):
{
"error": {
"type": "validation_error",
"code": "invalid_email",
"message": "Email must be a valid address.",
"field": "email",
"docs_url": "https://api.example.com/docs/errors#invalid_email"
}
}
Key ideas:
typegroups errors (validation_error, auth_error, rate_limit_error, server_error).codeis stable and machine‑readable.messageis for humans.docs_urlpoints to documentation.
This pattern plays nicely with RFC 9457 Problem Details for HTTP APIs (IETF), which standardizes a similar structure.
Trends for 2024–2025: standards and observability
API design in 2024–2025 is shaped by two big forces: formal standards and production observability. Both influence how you choose HTTP status codes.
Standardization: Problem Details and OpenAPI
The IETF’s Problem Details for HTTP APIs (RFC 9457, successor to RFC 7807) is increasingly used as a reference for error responses. While you don’t have to adopt it verbatim, aligning your error structure with it is a strong move.
OpenAPI 3.1 also makes it easier to document specific status codes per operation. Teams that publish their OpenAPI specs now routinely show:
200/201for successful operations.400,401,403,404,409,422for client errors.429for rate limiting.500,503for server issues.
Having explicit mappings in your spec is one of the best examples of best practices for HTTP status codes in APIs, because it keeps docs, code, and tests aligned.
Observability and SLOs
Most organizations now use API gateways and monitoring platforms that group metrics by status code family (2xx, 4xx, 5xx). Clean use of codes directly affects your service‑level objectives (SLOs):
- 5xx spikes trigger alerts.
- 4xx spikes often indicate integration bugs or abuse.
- 2xx/3xx patterns reveal normal traffic.
Consistent status codes make it easier to track error budgets and reliability goals, something emphasized in site reliability engineering guidance from organizations like Google and academic programs such as those at MIT.
Security and privacy: subtle status code choices
Some of the best examples of best practices for HTTP status codes in APIs come from security‑sensitive domains (health, finance, government).
Example of hiding resource existence
For endpoints that expose personal data (health records, financial accounts), returning 404 Not Found even when the resource exists but the caller lacks permission is often safer than 403 Forbidden. This avoids confirming that a record exists for a given identifier.
This pattern aligns with broader privacy principles advocated by organizations like the National Institute of Standards and Technology (NIST), which emphasize minimizing information disclosure.
Example of login and account lockout
For authentication endpoints:
- Invalid credentials:
401 Unauthorizedwith a generic message like"Invalid username or password.". - Locked account after too many attempts: still
401 Unauthorized, but with a different internalerror.codeand a safe public message.
Avoid distinguishing “user not found” vs. “wrong password” in the status code or public message; keep that distinction internal.
Putting it all together: a practical status code policy
If you’re writing team guidelines, you can summarize these examples of best practices for HTTP status codes in APIs into a short policy:
- Use
201for creates,200/204for reads/updates/deletes. - Use
400for malformed or invalid input, with structured validation details. - Use
401for missing/invalid auth,403for insufficient permissions. - Use
404for missing or hidden resources. - Use
409for conflicts and422for semantic errors. - Use
429for rate limiting, withRetry-Afterand rate limit headers. - Use
5xxonly for server faults, never for expected client mistakes. - Keep a consistent JSON error schema across all 4xx/5xx responses.
These patterns are not the only way to design an API, but they represent some of the best examples in production systems today. If you stick to them, your consumers will spend less time guessing and more time shipping.
FAQ: examples of status code usage in real APIs
What are some real examples of best practices for HTTP status codes in APIs?
Public APIs from companies like Stripe, GitHub, and Twilio consistently:
- Use
201 CreatedwithLocationheaders for resource creation. - Use
400for validation errors with field‑level details. - Use
401/403distinctly for auth vs. authorization. - Use
404for missing resources and sometimes for hidden ones. - Use
429for rate limits, withRetry-Afterand limit headers.
These are widely cited as best examples because they’re predictable and well‑documented.
Can you give an example of when to use 422 instead of 400?
Use 422 Unprocessable Entity when the JSON is valid and the fields are syntactically correct, but the request cannot be applied in its current form. For example, submitting a transfer where the source and destination accounts are the same, or booking an appointment in the past. Use 400 Bad Request when the JSON is malformed or required fields are missing.
Are 3xx redirects useful in APIs, and what are examples of good usage?
Yes, especially for long‑running operations or versioned resources. An example of best practice is returning 202 Accepted for a job submission, then 303 See Other from the job status endpoint to the final resource when it’s ready. Another example is using 301 Moved Permanently when you permanently change a resource path and want clients to update their references.
Should my API ever return 200 OK for errors?
No. Returning 200 OK for error conditions forces clients to inspect the body to detect failures and breaks monitoring tools that rely on status codes. Instead, use the appropriate 4xx or 5xx code and pair it with a structured error response. This is one of the simplest examples of best practices for HTTP status codes in APIs that immediately improves observability.
Where can I learn more about HTTP status codes and API error design?
Good starting points include the IETF’s HTTP specifications and related documents, such as RFC 9110 for HTTP semantics and RFC 9457 Problem Details for HTTP APIs. For applied API design principles, many university CS programs (for example, Harvard’s CS50 materials) include modern web programming content that touches on HTTP behavior and API patterns.
Related Topics
Explore More Best Practices for API Design
Discover more examples and insights in this category.
View All Best Practices for API Design