Fundamentals
Structured errors
A good error response says what went wrong, whether to retry and how to fix it, in a form code can read. Use status codes, a stable error code and an ETag for concurrent edits.
When a call fails, the caller's code has to decide what to do next. Retry it? Show the user a message? Fix the input? An error response should answer those questions in a machine-readable form. RFC 9457 "Problem Details for HTTP APIs" standardizes one shape for this, and it replaces the older RFC 7807.
Concept
A status code is the three-digit number in an HTTP response. 4xx means the client sent something wrong. 5xx means the server or something behind it failed.
The big idea
Callers should branch on the status code and on a stable code field. They should never parse the English text in detail. Retry rules follow from the status:
- 4xx: don't retry the same request unchanged. The exceptions are 408 and 429.
- 5xx and 429: retry with backoff. Include
Retry-Afterwhen you know the wait.
A precondition is a check that a request must pass before the server acts on it. Concurrent edits are the common case.
Walking through it
The codes that matter most:
| Code | Meaning | What the client should do |
|---|---|---|
| 400 | Malformed request | Fix the request. Don't retry |
| 401 / 403 | Not logged in / not allowed | Re-authenticate, or stop |
| 404 | Not found | Don't retry |
| 409 | Conflict, such as a duplicate | Re-read and reconcile |
| 412 | Precondition failed (If-Match mismatch) | Re-read, reapply the change |
| 422 | Input fails validation | Fix the input |
| 429 | Rate limited | Wait for Retry-After, then retry |
| 500 / 502 / 503 / 504 | Server or upstream failure | Retry with backoff, if the operation is idempotent |
Concurrent edits are where ETags help. The server returns an ETag, a version hash, with each resource. Clients send it back in If-Match when they save. Two editors can't silently overwrite each other:
- Editor A reads the file with ETag
"v1". Editor B reads it with"v1"too. - A saves with
If-Match: "v1". The server accepts it and returns a new ETag,"v2". - B saves with
If-Match: "v1". The server returns 412, because the file is now"v2". - B re-reads the file, reapplies the change, and saves with
"v2".
Without the ETag, step 3 would silently replace A's edit. This is the lost update problem.
A response that follows RFC 9457 looks like this:
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/quota-exceeded",
"title": "Storage quota exceeded",
"status": 422,
"detail": "Uploading 3.2 GB would exceed your 100 GB plan by 1.1 GB.",
"code": "quota_exceeded",
"request_id": "req_8KfQ2"
}The code is what clients branch on. The request_id lets support find the trace.
Common mistakes
- Making clients parse
detail. Text changes. Stable codes don't. - Leaking internals. Stack traces, SQL and hostnames in public error bodies help attackers and tell no caller anything useful.
- Hiding retryability. A 400 and a 503 look alike to a client that only checks for "error." Make the status and the retry rule obvious.
Check yourself
- A client gets 412 on save. What should it do? Re-read the resource, reapply its change to the new version, and save with the new ETag.
- Which errors should a client not retry unchanged? 4xx errors such as 400, 404 and 422, except 408 and 429.
Key takeaways
- Use one error format with a stable
code, clear retryability and a request ID. - Status codes tell callers whether to retry. 4xx means fix the request. 5xx and 429 mean retry with backoff.
- Use ETags with
If-Matchso concurrent edits fail with 412 instead of silently overwriting.
Further learning
RFC 9457: Problem Details for HTTP APIs defines the error shape used here.
Next, read about webhooks and long-running operations for work that finishes after the response.
Related
API design
An API is a contract that outlives the code behind it. Design the styles, methods, pagination, errors and async flows so changes stay additive.
Rate limiting
Why systems cap how much work a client can send, which algorithm counts, where the limit lives and what the client is told. Start here for the rate-limiting map.
Safe methods and idempotency
Each HTTP method makes a promise about side effects. Knowing which methods are safe to repeat tells you when a retry is harmless and when it could charge a card twice.