Guide

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-After when 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:

CodeMeaningWhat the client should do
400Malformed requestFix the request. Don't retry
401 / 403Not logged in / not allowedRe-authenticate, or stop
404Not foundDon't retry
409Conflict, such as a duplicateRe-read and reconcile
412Precondition failed (If-Match mismatch)Re-read, reapply the change
422Input fails validationFix the input
429Rate limitedWait for Retry-After, then retry
500 / 502 / 503 / 504Server or upstream failureRetry 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:

  1. Editor A reads the file with ETag "v1". Editor B reads it with "v1" too.
  2. A saves with If-Match: "v1". The server accepts it and returns a new ETag, "v2".
  3. B saves with If-Match: "v1". The server returns 412, because the file is now "v2".
  4. 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
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

  1. 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.
  2. 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-Match so 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.