HTTP Status Codes for REST APIs: 200 vs 201 vs 204, 400 vs 422 vs 429

HTTP status codes are part of the REST contract between your API and its clients. Using the wrong code creates ambiguity, breaks client logic, and makes debugging harder. This guide covers the most confusing pairs with concrete rules for when to use each.

The Big Picture: Status Code Categories

RangeCategoryMeaning
1xxInformationalRequest received, continue processing
2xxSuccessRequest was successfully received and processed
3xxRedirectionFurther action needed to complete the request
4xxClient ErrorThe request contains an error — client's fault
5xxServer ErrorServer failed to fulfill a valid request — server's fault

Success Codes: 200 vs 201 vs 202 vs 204

200 OK — the default success

Use for GET (return the resource), PUT andPATCH when returning the updated resource, and any successful operation that returns a body.

GET /users/123 → 200 OK
{ "id": 123, "name": "Alice", "email": "alice@example.com" }

201 Created — resource was created

Use for successful POST requests that create a new resource. Must include a Location header pointing to the new resource.

POST /users
{ "name": "Bob", "email": "bob@example.com" }

→ 201 Created
Location: /users/124
{ "id": 124, "name": "Bob", "email": "bob@example.com" }

202 Accepted — async operation queued

Use when the request is valid and queued for processing, but processing is not complete. Return a job ID or a URL the client can poll for status.

POST /reports/generate
{ "type": "monthly", "month": "2026-02" }

→ 202 Accepted
{ "jobId": "job_abc123", "statusUrl": "/jobs/job_abc123" }

204 No Content — success, no body

Use for successful DELETE requests, or PUT/PATCH when you don't need to return the updated resource. Never include a response body with 204.

DELETE /users/123 → 204 No Content
(empty body)

Client Error Codes: 400 vs 422 vs 409

400 Bad Request — malformed or missing data

Use when the request is structurally broken: unparseable JSON, wrong Content-Type header, missing required request parameters, or invalid URL structure.

POST /users
Content-Type: text/plain   ← wrong content type
"not valid json"

→ 400 Bad Request
{ "error": "Content-Type must be application/json" }

422 Unprocessable Content — valid format, invalid data

Use when the request is syntactically correct (valid JSON, correct Content-Type) but semantically invalid. This is the right code for business logic validation errors.

POST /users
{ "name": "", "email": "not-an-email", "age": -5 }

→ 422 Unprocessable Content
{
  "error": "Validation failed",
  "fields": {
    "name":  "Name is required",
    "email": "Must be a valid email address",
    "age":   "Must be a positive integer"
  }
}

409 Conflict — state conflict

Use when the request is valid but conflicts with the current state: duplicate creation, optimistic locking failure (ETag mismatch), or concurrent modification.

POST /users
{ "email": "alice@example.com" }   ← already exists

→ 409 Conflict
{ "error": "A user with this email already exists" }

Authentication vs Authorization: 401 vs 403

This is one of the most frequently confused pairs:

  • 401 Unauthorized — identity unknown. No token, expired token, or invalid credentials. The response must include WWW-Authenticate. The name is misleading — it really means "unauthenticated."
  • 403 Forbidden — identity known, permission denied. The user is authenticated but lacks the required role or permission.
GET /admin/users  (no Authorization header)
→ 401 Unauthorized
WWW-Authenticate: Bearer realm="api"

GET /admin/users  (valid token for regular user)
→ 403 Forbidden
{ "error": "Admin role required" }

Security note: if revealing whether a resource exists is a risk (e.g., private user profiles), return 404 instead of 403. This prevents enumeration attacks.

Rate Limiting and Availability: 429 vs 503

Both indicate the request can't be served right now, but for different reasons:

# 429 — this client exceeded their rate limit
→ 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711148400

# 503 — server is overloaded or in maintenance
→ 503 Service Unavailable
Retry-After: 300
{ "error": "Service is temporarily unavailable. Please try again in 5 minutes." }

Server Errors: 500 vs 502 vs 503 vs 504

CodeCauseTypical source
500Unhandled exception in your codeApplication bug
502Upstream service returned garbageMicroservice crash
503Server can't handle requests nowOverload, maintenance
504Upstream service too slowDatabase, third-party API timeout

For 500 errors: never return stack traces or internal error details to clients. Log them server-side with a correlation ID. Return only the correlation ID to the client so they can report it to support.

Explore all HTTP status codes with REST API context in our HTTP Status Codes reference. For debugging authentication issues, use the JWT Decoder to inspect your token claims.

Frequently Asked Questions

Should I always use 200 for successful responses to keep things simple?

Using 200 for everything works but loses semantic information that clients and infrastructure depend on. Caching proxies treat 201 and 204 differently from 200. API gateways can route based on status codes. Monitoring tools distinguish creation (201) from updates (200). Clients can auto-follow 201 Location headers. The semantic richness of HTTP status codes is part of the REST architectural style — use them, but don't invent your own codes (using 200 with an error body is a well-known anti-pattern called 'HTTP 200 OK, Error: true').

What response body should I return with error status codes?

The RFC 7807 'Problem Details' standard defines a JSON format for API errors: { type: 'about:blank', title: 'Unprocessable Content', status: 422, detail: 'Email address is invalid', instance: '/users/create' }. This is the emerging standard used by many major APIs. At minimum, return a JSON body with a human-readable message, a machine-readable error code, and for 400/422, field-level validation errors. Never return an empty body with a 4xx status — the client needs to know what went wrong.

When should I use 503 vs 429?

429 Too Many Requests is a client-side rate limit — the specific user or API key has exceeded their quota. It's targeted and temporary: this client sent too many requests. 503 Service Unavailable is server-side capacity — the entire service is overloaded or down for maintenance and cannot serve any request right now. Both should include Retry-After headers. Use 429 in rate limiting middleware, 503 in health check endpoints and during deployments or database overload.