Complete Guide to HTTP Status Codes

Learn when to use 200, 201, 204, 400, 404, 422, and 500 status codes in REST APIs. Complete guide with examples from Modern PetStore API.

TRY NANO BANANA FOR FREE

Complete Guide to HTTP Status Codes

TRY NANO BANANA FOR FREE
Contents

Your API returns 200 OK for everything. Successful creation? 200 OK. Validation error? 200 OK with an error message in the body. Server crash? 200 OK with {"success": false}.

This is wrong. HTTP status codes exist for a reason. They tell clients what happened without parsing the response body. They enable proper error handling, caching, and retry logic.

Here's how to use HTTP status codes correctly in REST APIs.

The Five Status Code Categories

HTTP status codes are grouped into five categories:

  • 1xx (Informational): Request received, continuing process
  • 2xx (Success): Request successfully received, understood, and accepted
  • 3xx (Redirection): Further action needed to complete the request
  • 4xx (Client Error): Request contains bad syntax or can't be fulfilled
  • 5xx (Server Error): Server failed to fulfill a valid request

Most REST APIs use 2xx, 4xx, and 5xx codes. Let's break down when to use each one.

Success Codes (2xx)

200 OK

Use 200 for successful GET, PUT, or PATCH requests that return a response body.

GET request:

GET /pets/019b4132-70aa-764f-b315-e2803d882a24

Response: 200 OK
{
  "id": "019b4132-70aa-764f-b315-e2803d882a24",
  "name": "Max",
  "species": "DOG",
  "status": "AVAILABLE"
}

PUT request (full replacement):

PUT /pets/019b4132-70aa-764f-b315-e2803d882a24
{
  "name": "Max",
  "species": "DOG",
  "status": "ADOPTED"
}

Response: 200 OK
{
  "id": "019b4132-70aa-764f-b315-e2803d882a24",
  "name": "Max",
  "species": "DOG",
  "status": "ADOPTED",
  "updatedAt": "2026-03-13T10:30:00Z"
}

PATCH request (partial update):

PATCH /pets/019b4132-70aa-764f-b315-e2803d882a24
{
  "status": "ADOPTED"
}

Response: 200 OK
{
  "id": "019b4132-70aa-764f-b315-e2803d882a24",
  "name": "Max",
  "species": "DOG",
  "status": "ADOPTED",
  "updatedAt": "2026-03-13T10:30:00Z"
}

Don't use 200 for POST (creation) or DELETE operations. Those have better status codes.

201 Created

Use 201 when a POST request successfully creates a resource.

POST /pets
{
  "name": "Bella",
  "species": "CAT",
  "breed": "Siamese"
}

Response: 201 Created
Location: /pets/019b4145-8f3a-7c2d-a1b5-f4e8d9c3a7b2
{
  "id": "019b4145-8f3a-7c2d-a1b5-f4e8d9c3a7b2",
  "name": "Bella",
  "species": "CAT",
  "breed": "Siamese",
  "status": "AVAILABLE",
  "createdAt": "2026-03-13T10:35:00Z"
}

The Location header tells clients where to find the new resource. This is important for clients that need to perform follow-up operations.

202 Accepted

Use 202 when the request is valid but processing happens asynchronously.

POST /pets/019b4132-70aa-764f-b315-e2803d882a24/medical-records
{
  "type": "VACCINATION",
  "vaccine": "Rabies",
  "date": "2026-03-13"
}

Response: 202 Accepted
{
  "id": "019b4150-2a1f-6d4e-b8c3-e7f9a2d5c1b8",
  "status": "PROCESSING",
  "statusUrl": "/medical-records/019b4150-2a1f-6d4e-b8c3-e7f9a2d5c1b8/status"
}

The response includes a URL where clients can check processing status. This pattern works well for long-running operations like file processing, report generation, or external API calls.

204 No Content

Use 204 for successful DELETE requests or updates that don't return a body.

DELETE request:

DELETE /pets/019b4132-70aa-764f-b315-e2803d882a24

Response: 204 No Content

No response body. The status code alone confirms success.

PUT request without response:

PUT /users/019b4155-7e2c-5a3f-d9b4-c8e1f3a6b2d7/preferences
{
  "emailNotifications": true,
  "theme": "dark"
}

Response: 204 No Content

Use 204 when the client doesn't need the updated resource. This saves bandwidth.

Client Error Codes (4xx)

400 Bad Request

Use 400 for malformed requests—invalid JSON, missing required headers, or syntax errors.

POST /pets
{
  "name": "Max",
  "species": "INVALID_SPECIES"  ← Not a valid enum value
}

Response: 400 Bad Request
{
  "type": "https://petstoreapi.com/errors/bad-request",
  "title": "Bad Request",
  "status": 400,
  "detail": "Request body contains invalid data",
  "instance": "/v1/pets"
}

Don't use 400 for validation errors. Use 422 instead (see below).

401 Unauthorized

Use 401 when authentication is required but missing or invalid.

GET /pets/019b4132-70aa-764f-b315-e2803d882a24

Response: 401 Unauthorized
WWW-Authenticate: Bearer realm="PetStore API"
{
  "type": "https://petstoreapi.com/errors/unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Authentication required. Provide a valid access token."
}

The WWW-Authenticate header tells clients how to authenticate.

403 Forbidden

Use 403 when the user is authenticated but lacks permission.

DELETE /pets/019b4132-70aa-764f-b315-e2803d882a24

Response: 403 Forbidden
{
  "type": "https://petstoreapi.com/errors/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "You don't have permission to delete this pet",
  "requiredScope": "write:pets"
}

The difference between 401 and 403: - 401: "Who are you?" (authentication problem) - 403: "I know who you are, but you can't do that" (authorization problem)

404 Not Found

Use 404 when the requested resource doesn't exist.

GET /pets/019b4999-9999-9999-9999-999999999999

Response: 404 Not Found
{
  "type": "https://petstoreapi.com/errors/not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "Pet not found",
  "instance": "/v1/pets/019b4999-9999-9999-9999-999999999999"
}

Also use 404 for invalid endpoints:

GET /invalid-endpoint

Response: 404 Not Found

405 Method Not Allowed

Use 405 when the HTTP method isn't supported for the endpoint.

POST /pets/019b4132-70aa-764f-b315-e2803d882a24

Response: 405 Method Not Allowed
Allow: GET, PUT, PATCH, DELETE
{
  "type": "https://petstoreapi.com/errors/method-not-allowed",
  "title": "Method Not Allowed",
  "status": 405,
  "detail": "POST is not allowed for this endpoint",
  "allowedMethods": ["GET", "PUT", "PATCH", "DELETE"]
}

The Allow header lists supported methods.

409 Conflict

Use 409 when the request conflicts with the current state.

POST /pets
{
  "id": "019b4132-70aa-764f-b315-e2803d882a24",  ← ID already exists
  "name": "Max"
}

Response: 409 Conflict
{
  "type": "https://petstoreapi.com/errors/conflict",
  "title": "Conflict",
  "status": 409,
  "detail": "A pet with this ID already exists"
}

Also use 409 for concurrent modification conflicts:

PUT /pets/019b4132-70aa-764f-b315-e2803d882a24
If-Match: "v1"

Response: 409 Conflict
{
  "type": "https://petstoreapi.com/errors/conflict",
  "title": "Conflict",
  "status": 409,
  "detail": "Resource was modified by another request",
  "currentVersion": "v2"
}

422 Unprocessable Entity

Use 422 for validation errors—the request is well-formed but contains invalid data.

POST /pets
{
  "name": "",  ← Empty name
  "species": "DOG",
  "age": -5  ← Negative age
}

Response: 422 Unprocessable Entity
{
  "type": "https://petstoreapi.com/errors/validation-error",
  "title": "Validation Error",
  "status": 422,
  "detail": "The request body contains validation errors",
  "errors": [
    {
      "field": "name",
      "message": "Name cannot be empty"
    },
    {
      "field": "age",
      "message": "Age must be a positive number"
    }
  ]
}

The errors array lists all validation failures. This helps clients fix multiple issues at once.

429 Too Many Requests

Use 429 when the client exceeds rate limits.

GET /pets

Response: 429 Too Many Requests
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1678886400
Retry-After: 3600
{
  "type": "https://petstoreapi.com/errors/rate-limit-exceeded",
  "title": "Rate Limit Exceeded",
  "status": 429,
  "detail": "You have exceeded the rate limit of 1000 requests per hour",
  "retryAfter": 3600
}

The Retry-After header tells clients when they can retry.

Server Error Codes (5xx)

500 Internal Server Error

Use 500 for unexpected server errors.

GET /pets/019b4132-70aa-764f-b315-e2803d882a24

Response: 500 Internal Server Error
{
  "type": "https://petstoreapi.com/errors/internal-error",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "An unexpected error occurred",
  "instance": "/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
  "traceId": "abc123def456"
}

Include a traceId for debugging. Don't expose stack traces or internal details in production.

502 Bad Gateway

Use 502 when an upstream service fails.

GET /pets/019b4132-70aa-764f-b315-e2803d882a24/weather

Response: 502 Bad Gateway
{
  "type": "https://petstoreapi.com/errors/bad-gateway",
  "title": "Bad Gateway",
  "status": 502,
  "detail": "Weather service is unavailable"
}

This tells clients the problem is with a dependency, not your API.

503 Service Unavailable

Use 503 during maintenance or when the service is temporarily down.

GET /pets

Response: 503 Service Unavailable
Retry-After: 300
{
  "type": "https://petstoreapi.com/errors/service-unavailable",
  "title": "Service Unavailable",
  "status": 503,
  "detail": "The service is temporarily unavailable for maintenance",
  "retryAfter": 300
}

The Retry-After header tells clients when to try again.

504 Gateway Timeout

Use 504 when an upstream service times out.

GET /pets/019b4132-70aa-764f-b315-e2803d882a24/recommendations

Response: 504 Gateway Timeout
{
  "type": "https://petstoreapi.com/errors/gateway-timeout",
  "title": "Gateway Timeout",
  "status": 504,
  "detail": "Recommendation service did not respond in time"
}

Common Mistakes

Mistake 1: Using 200 for Everything

Don't return 200 with {"success": false} in the body. Use proper error codes.

Bad:

POST /pets
{
  "name": ""
}

Response: 200 OK
{
  "success": false,
  "error": "Name is required"
}

Good:

Response: 422 Unprocessable Entity
{
  "type": "https://petstoreapi.com/errors/validation-error",
  "title": "Validation Error",
  "status": 422,
  "detail": "Name is required"
}

Mistake 2: Using 200 for Creation

Don't return 200 for POST requests that create resources. Use 201.

Bad:

POST /pets

Response: 200 OK

Good:

Response: 201 Created
Location: /pets/019b4132-70aa-764f-b315-e2803d882a24

Mistake 3: Using 200 for Deletion

Don't return 200 with a body for DELETE requests. Use 204.

Bad:

DELETE /pets/019b4132-70aa-764f-b315-e2803d882a24

Response: 200 OK
{
  "message": "Pet deleted successfully"
}

Good:

Response: 204 No Content

Mistake 4: Confusing 400 and 422

Use 400 for malformed requests, 422 for validation errors.

400 example (malformed JSON):

POST /pets
{
  "name": "Max",
  "species": "DOG"  ← Missing closing brace

Response: 400 Bad Request

422 example (valid JSON, invalid data):

POST /pets
{
  "name": "",
  "species": "DOG"
}

Response: 422 Unprocessable Entity

Mistake 5: Confusing 401 and 403

Use 401 for authentication problems, 403 for authorization problems.

401 example (no token):

GET /pets

Response: 401 Unauthorized

403 example (valid token, insufficient permissions):

DELETE /pets/019b4132-70aa-764f-b315-e2803d882a24

Response: 403 Forbidden

Status Code Decision Tree

Use this flowchart to choose the right status code:

  1. Was the request successful?
  2. Yes → Go to step 2

No → Go to step 5

Did you create a resource?

  1. Yes → 201 Created

No → Go to step 3

Are you returning a response body?

  1. Yes → 200 OK

No → 204 No Content

Is processing asynchronous?

  1. Yes → 202 Accepted

No → 200 OK

Is it a client error?

  1. Yes → Go to step 6

No → Go to step 10

Is the request malformed?

  1. Yes → 400 Bad Request

No → Go to step 7

Is authentication missing or invalid?

  1. Yes → 401 Unauthorized

No → Go to step 8

Does the user lack permission?

  1. Yes → 403 Forbidden

No → Go to step 9

Does the resource not exist?

  1. Yes → 404 Not Found

No → Check for 405, 409, 422, or 429

Is it a server error?

  • Internal error → 500 Internal Server Error
  • Upstream failure → 502 Bad Gateway
  • Service down → 503 Service Unavailable
  • Upstream timeout → 504 Gateway Timeout

Best Practices

1. Be Consistent

Use the same status codes for the same situations across your API. Don't return 200 for creation in one endpoint and 201 in another.

2. Include Error Details

Always return a structured error response with: - Error type (URL to documentation) - Human-readable title - Detailed message - Status code - Request instance (the URL that failed)

Follow RFC 9457 Problem Details format.

3. Use Standard Headers

Include relevant headers: - Location for 201 responses - WWW-Authenticate for 401 responses - Allow for 405 responses - Retry-After for 429 and 503 responses

4. Don't Expose Internal Details

In production, don't return stack traces, database errors, or internal paths. Log these server-side and return a generic error to clients.

5. Document Your Status Codes

List all possible status codes for each endpoint in your API documentation. Include example responses for each code.

Testing Status Codes

Test that your API returns correct status codes:

// Test 201 for creation
const response = await fetch('/pets', {
  method: 'POST',
  body: JSON.stringify({name: 'Max', species: 'DOG'})
});
assert(response.status === 201);
assert(response.headers.get('Location'));

// Test 404 for missing resource
const response = await fetch('/pets/invalid-id');
assert(response.status === 404);

// Test 422 for validation errors
const response = await fetch('/pets', {
  method: 'POST',
  body: JSON.stringify({name: '', species: 'DOG'})
});
assert(response.status === 422);

Use tools like Apidog to test all status code scenarios and ensure your API behaves correctly.

Conclusion

HTTP status codes are not optional. They're part of the HTTP specification and enable proper client behavior.

Use 2xx for success, 4xx for client errors, 5xx for server errors. Be specific: 201 for creation, 204 for deletion, 422 for validation, 401 for authentication, 403 for authorization.

Your clients will thank you. Their error handling will be simpler, their retry logic will work correctly, and their caching will be more efficient.

Stop returning 200 for everything. Use the right status code for the right situation.