Skip to course content
Free FastAPI backend course

FastAPI for AI Backend Development

Unit 06.01: Errors clients can handle

An error body needs a stable code a client can branch on.

Machine-readable alongside human-readable

A lookup returning a structured error with a code and the offending identifier.

The code shows both the success and the failure.

from fastapi import FastAPI, HTTPException
from fastapi.testclient import TestClient

app = FastAPI()
INVOICES = {"INV-1": {"id": "INV-1", "amount": 240.0}}


@app.get("/invoices/{invoice_id}")
def get_invoice(invoice_id: str) -> dict:
    invoice = INVOICES.get(invoice_id)
    if invoice is None:
        raise HTTPException(status_code=404,
                            detail={"error": "invoice_not_found",
                                    "invoice_id": invoice_id})
    return invoice


client = TestClient(app)
for invoice_id in ("INV-1", "INV-9"):
    r = client.get(f"/invoices/{invoice_id}")
    print(f"{invoice_id}: {r.status_code} {r.json()}")

print("""
The error body carries a stable machine-readable code alongside the human
text. A client can branch on `invoice_not_found`; it cannot branch on a
sentence that may be reworded next release.
""")

invoice_not_found is stable; the sentence beside it can be reworded, translated or improved without breaking anyone. A client branching on prose breaks the first time someone fixes a typo.

Including the identifier is what makes the error actionable - the caller knows which of the twenty invoices they requested was missing.

The mistake this prevents

The mistake is returning only a human sentence. It reads well in a browser and gives an automated caller nothing to switch on, so they end up matching on substrings - and then your wording is part of the contract whether you meant it or not.

Takeaway

Every error body needs a stable machine-readable code and enough context to act on. Prose is for humans and must not be what clients branch on.