Standard Error Responses with Problem Details (RFC 9457)

Intermediate
11 min

Standard Error Responses with Problem Details (RFC 9457)

Every API invents its own error shape: { "error": "..." } here, { "message": "...", "code": 17 } there, sometimes a bare string. Clients end up with special-case parsing for each service they call. RFC 9457, Problem Details for HTTP APIs, defines one machine-readable error format. In this lesson you will learn its members, how to extend it for validation errors, and how to produce it from a single Express error middleware.

Why a Standard Format

An HTTP status code says what kind of failure occurred, not why. 403 could mean an expired subscription or a missing scope; 422 could mean any of twenty validation rules. Problem Details gives every error a stable identity (type) plus a human-readable explanation, so clients can branch on type and show detail to users. The standardized media type application/problem+json lets SDK generators and logging tools recognize it too.

Anatomy of a Problem Details Object

| Member | Type | Meaning | |---|---|---| | type | URI | Error category; defaults to about:blank when the status code says it all | | title | string | Short summary, identical for every occurrence of this type | | status | integer | The HTTP status code, repeated for convenience | | detail | string | Human-readable explanation of this occurrence | | instance | URI | Identifies this occurrence, often the request path or a trace id |

The type URI need not resolve, but serving documentation there is good practice. Any other member is an extension; clients must ignore members they do not understand.

json
HTTP/1.1 403 Forbidden Content-Type: application/problem+json { "type": "https://api.example.com/problems/subscription-expired", "title": "Subscription expired", "status": 403, "detail": "Your Team plan expired on 2026-09-01. Renew to keep creating projects.", "instance": "/projects", "renewUrl": "/billing" }

Validation Errors as an Extension

Validation failures usually involve several fields at once. Add an errors array as an extension member, one entry per field, using a JSON Pointer so the client can highlight the right input:

json
HTTP/1.1 422 Unprocessable Content Content-Type: application/problem+json { "type": "https://api.example.com/problems/validation-error", "title": "Request body is invalid", "status": 422, "errors": [ { "pointer": "/email", "message": "must be a valid email address" }, { "pointer": "/age", "message": "must be at least 13" } ] }

This shape is what most validation libraries produce with a small mapping function, and it keeps the top-level structure identical to every other error.

Producing Problem Details in Express

Define one error class and let a single error-handling middleware format every failure:

javascript
class Problem extends Error { constructor(status, title, detail, extra = {}) { super(detail); Object.assign(this, { status, title, detail, ...extra }); } } app.get("/books/:id", (req, res, next) => { const book = db.get(req.params.id); if (!book) return next(new Problem(404, "Book not found", `No book with id ${req.params.id}`)); res.json(book); }); app.use((err, req, res, next) => { const status = err.status ?? 500; const { title = "Internal Server Error", detail, ...extra } = status === 500 ? {} : err; res.status(status).type("application/problem+json").json({ type: err.type ?? "about:blank", title, status, detail: status === 500 ? undefined : detail, instance: req.originalUrl, ...extra, }); });

Unexpected exceptions become a generic 500 with no detail, so stack traces and database messages never leak. Log the original error with a request id and put that id in instance so support can correlate reports with logs.

Handling Problems on the Client

Clients should check Content-Type before parsing: application/problem+json means the body follows this contract, even for a status they did not anticipate. Branch on type for decisions and display detail to the user.

Tips

  • Keep title constant per type; put the changing information in detail and extensions.
  • Use 422 for semantic validation failures and 400 for unparsable requests.
Quick Quiz
Question 1 of 2

Which `Content-Type` marks a response body as a Problem Details object?

Key Takeaways

  • RFC 9457 defines a standard error body with type, title, status, detail and instance.
  • Serve it with Content-Type: application/problem+json so clients can detect it reliably.
  • Extension members such as an errors array carry field-level validation information.
  • One Express error middleware can format every failure and hide internals on 500.
  • Clients branch on type and show detail; they never depend on the wording of title.

Next lesson: HTTP Caching: ETag, Cache-Control and Conditional Requests — make repeated reads cheap and protect writes from lost updates.

Standard Error Responses with Problem Details (RFC 9457) - REST APIs | CodeYourCraft | CodeYourCraft