Choosing the Right REST API Status Code

When to use 200/201/204, 400/401/403/404/409/422/429, and 500/503, plus consistency and idempotency-key design.

In a REST API, the status code is a contract that lets clients understand a response without reading your docs. A well-chosen code tells the client whether to retry, what to show the user, and whether it can cache. An API that returns 200 for everything and hides a success flag in the body forces clients to parse every response and throws away the benefits of HTTP infrastructure — caches, proxies, and monitoring.

Success: 200 / 201 / 204

POST /v1/users HTTP/1.1
Content-Type: application/json

{"email":"a@example.com"}

HTTP/1.1 201 Created
Location: /v1/users/u_92311
Content-Type: application/json

{"id":"u_92311","email":"a@example.com"}

Client errors: 400 / 401 / 403 / 404 / 409 / 422 / 429

Distinguishing 400 from 422 trips up many teams. By convention, 400 means 'the server couldn't even parse the request' (broken body), while 422 means 'it parsed, but a field failed a validation rule.' Picking one convention and applying it consistently across the whole API matters more than any individual choice.

Server errors and retries: 500 / 503

500 Internal Server Error is the catch-all for an unexpected server-side exception. 503 Service Unavailable signals a temporary inability to handle the request — overload or maintenance — and can advertise a recovery time via Retry-After. The 5xx codes and 429 are the ones a client may safely retry with exponential backoff.

Retrying safely with idempotency keys

On a flaky network, creating a payment or order with POST can leave you in the 'request arrived but the response was lost' state. Blindly retrying risks a duplicate. The fix: the client sends a unique value in an Idempotency-Key header, and the server executes a given key only once, then replays the stored original response for any later request bearing the same key.

POST /v1/charges HTTP/1.1
Idempotency-Key: 3f9c1b7a-2e5d-4a11-9c8e-77b0a1d2e4f6
Content-Type: application/json

{"amount":5000,"currency":"krw"}

Consistency is the ultimate goal of status-code design. Use the same code for the same kind of situation every time, standardize your error body (for example an error object containing code and message fields), and document what each code means. That lets client developers write predictable, defensive handling.