Real‑world examples of best practices for HTTP status codes in APIs

If you build or maintain APIs, you already know that HTTP status codes either make your life easier or quietly sabotage every integration. This guide focuses on **real examples of best practices for HTTP status codes in APIs**, not theory for theory’s sake. We’ll walk through how modern teams actually use 2xx, 3xx, 4xx, and 5xx codes, and why consistent choices matter for observability, client SDKs, and long‑term maintenance. You’ll see examples of how Stripe, GitHub, and other public APIs structure their responses, and how to avoid the vague `200 OK` for everything anti‑pattern. Along the way, we’ll look at examples of best practices for HTTP status codes in APIs that cover validation errors, rate limiting, pagination, idempotency, and more. By the end, you’ll have a practical playbook you can drop into your API guidelines, plus patterns that align with modern HTTP standards and the direction of the broader web ecosystem.
Written by
Jamie
Published
Updated

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 /users201 Created
    • Include a Location header pointing to the new resource: Location: /users/123.
    • Response body: the newly created user object.
  • Read: GET /users/123200 OK
    • Response body: current representation of the user.
  • Update (PUT/PATCH): PATCH /users/123200 OK or 204 No Content
    • Use 200 if you return the updated resource.
    • Use 204 if you return no body.
  • Delete: DELETE /users/123204 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 /videos202 Accepted
    • Body contains a job ID and status URL: { "job_id": "abc123", "status_url": "/jobs/abc123" }.
  • GET /jobs/abc123
    • 200 OK with { "status": "pending" } or { "status": "completed" }.
    • 303 See Other redirecting 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-Authenticate header 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/users401 Unauthorized
    • Body: { "error": { "type": "auth_error", "message": "Missing access token." } }
  • Insufficient role: GET /admin/users with a basic user token → 403 Forbidden
    • Body: { "error": { "type": "authorization_error", "message": "Admin role required." } }

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) → still 404 Not Found with 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/current when only GET is supported.
    • Include an Allow header: Allow: GET, DELETE.
  • 415 Unsupported Media Type: Payload type not supported.
    • POST /users with Content-Type: text/plain when only application/json is 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-After header when you expect recovery: Retry-After: 120.
  • 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 Conflict when a resource already exists in a way that conflicts with the request.
    • POST /users with email that already exists.
  • 422 Unprocessable Entity when the payload is syntactically valid JSON but semantically invalid.
    • POST /transfers where amount is greater than the account balance.

Real example pattern:

  • POST /users with duplicate email → 409 Conflict.
  • POST /transfers with amount -10422 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:

  • type groups errors (validation_error, auth_error, rate_limit_error, server_error).
  • code is stable and machine‑readable.
  • message is for humans.
  • docs_url points to documentation.

This pattern plays nicely with RFC 9457 Problem Details for HTTP APIs (IETF), which standardizes a similar structure.


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 / 201 for successful operations.
  • 400, 401, 403, 404, 409, 422 for client errors.
  • 429 for rate limiting.
  • 500, 503 for 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 Unauthorized with a generic message like "Invalid username or password.".
  • Locked account after too many attempts: still 401 Unauthorized, but with a different internal error.code and 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 201 for creates, 200/204 for reads/updates/deletes.
  • Use 400 for malformed or invalid input, with structured validation details.
  • Use 401 for missing/invalid auth, 403 for insufficient permissions.
  • Use 404 for missing or hidden resources.
  • Use 409 for conflicts and 422 for semantic errors.
  • Use 429 for rate limiting, with Retry-After and rate limit headers.
  • Use 5xx only 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 Created with Location headers for resource creation.
  • Use 400 for validation errors with field‑level details.
  • Use 401 / 403 distinctly for auth vs. authorization.
  • Use 404 for missing resources and sometimes for hidden ones.
  • Use 429 for rate limits, with Retry-After and 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.

Explore More Best Practices for API Design

Discover more examples and insights in this category.

View All Best Practices for API Design