REST APIs in Depth · Correctness: Errors, Validation, Concurrency and Caching

Error Responses with Problem Details (RFC 9457)

One machine-readable error format for every endpoint.

RFC 9457 defines application/problem+json with type (a URI identifying the problem kind), title, status, detail and instance, plus extension members. Use stable type URIs (documented pages), human-readable title/detail, and extensions like errors (field-level) and request_id. Never leak stack traces or SQL. Clients branch on status and type, never on message text.

def problem(status: int, type_: str, title: str, detail: str | None = None, **ext):
    body = {"type": f"https://api.example.com/problems/{type_}", "title": title, "status": status, **ext}
    if detail:
        body["detail"] = detail
    return JSONResponse(body, status_code=status, media_type="application/problem+json")

@app.exception_handler(OutOfStock)
async def out_of_stock(request: Request, exc: OutOfStock):
    return problem(409, "out-of-stock", "Item out of stock", f"Only {exc.available} left", sku=exc.sku)

Common pitfalls

  • Different error shapes per endpoint or team.
  • Clients parsing detail text to decide behavior.
  • Exposing internal exception messages.

Key points

  • application/problem+json everywhere
  • Stable type URIs; extensions for details
  • request_id for support

Further reading: RFC 9457: Problem Details for HTTP APIs

Practice questions

Why should clients branch on type rather than title or detail?

Answer

Titles and details are human-readable and may change or be localized; the type URI is a stable identifier designed for programmatic handling.

What extension members are commonly added to problem details?

Answer

Field-level errors for validation, a request_id/trace id for support, and domain data like sku, available or retry_after.

Open in the app to mark lessons complete, take notes and track your progress.