Problem Details

RFC 9457 Problem Details for HTTP APIs — standardized error response envelope with type / title / status / detail / instance plus operator-supplied extensions. Sets Content-Type: application/problem+json per RFC 9457 §3 so clients can branch on the content type rather than scanning ad- hoc JSON shapes. Supersedes RFC 7807 (obsolete).

Operators wire this in three places: 1. b.problemDetails.create({...}) builds a problem object with field validation (type URI, status range, etc.). 2. b.problemDetails.respond(res, problem) serializes + sends. 3. b.problemDetails.fromError(err) converts a thrown FrameworkError into the matching problem document; the err.code (e.g. csv/invalid-record) becomes the type URI suffix (https://blamejs.com/problems/csv/ invalid-record).

b.problemDetails.validate(doc) parses an INBOUND problem response (e.g. when blamejs is the client of an upstream API returning RFC 9457) — refuses non-objects, refuses bad type URIs, refuses status outside 100..599.

Tier choices per feedback_validation_tier_policy.md: - create / fromError / validate — THROW on bad input (config-time / entry-point shape). - respond — THROW on bad input (its first call sets the response shape; silent drop would mask a programming bug).

b.problemDetails.setBase(baseUri) #

stable0.8.84

Override the base URI prepended to error-code-derived type URIs. Default is https://blamejs.com/problems. Operators running under their own published vocabulary (e.g. an internal status page or an organization-owned problem catalog) point this at the canonical location. Throws problem-details/bad-base for non-https / non- absolute / non-string inputs.

b.problemDetails.setBase("https://api.example.com/problems");
b.problemDetails.fromError(new Error("csv/invalid-record")).type;
// → "https://api.example.com/problems/csv/invalid-record"

b.problemDetails.getBase() #

stable0.8.84

Read the currently-configured base URI. Useful for diagnostic logging and tests.

b.problemDetails.getBase();   // → "https://blamejs.com/problems"

b.problemDetails.create(opts) #

stable0.8.84
{
  type:       string,   // problem-type URI reference (default "about:blank")
  title:      string,   // short summary
  status:     number,   // integer 100..599
  detail:     string,   // human-readable explanation
  instance:   string,   // URI reference for this specific occurrence
  extensions: object,   // keys spread as top-level siblings (§3.2); direct top-level key wins on collision
  ...extensions         // additional top-level keys preserved as-is
}

Build a frozen RFC 9457 problem-details object. Validates the standard fields per §3: - type (optional, defaults to "about:blank") must be a URI reference (string); MAY be relative or absolute. - title (recommended) must be a non-empty string when given. - status (recommended) must be an integer 100..599. - detail (optional) must be a string when given. - instance (optional) must be a URI reference string when given. - Extensions: every additional top-level key whose name is NOT in RESERVED_FIELDS is preserved at the top level. Reserved-name collisions throw problem-details/reserved-extension; prototype-pollution-shaped top-level keys throw the same. - extensions: a plain object whose keys are spread as top-level sibling members (RFC 9457 §3.2) — the literal extensions member is never emitted. Keys colliding with RESERVED_FIELDS are ignored (reserved fields can't be overridden by an extension); prototype-pollution-shaped keys are dropped silently. When the same name appears both as a direct top-level key and inside extensions, the direct top-level key wins.

Returns a frozen plain object suitable for JSON.stringify.

var p = b.problemDetails.create({
  type:     "https://example.com/problems/out-of-credit",
  title:    "You do not have enough credit.",
  status:   403,
  detail:   "Your current balance is 30, but that costs 50.",
  instance: "/account/12345/msgs/abc",
  balance:  30,
  accounts: ["/account/12345", "/account/67890"],
});
// → {
//     type: "https://example.com/problems/out-of-credit",
//     title: "You do not have enough credit.",
//     status: 403,
//     detail: "Your current balance is 30, but that costs 50.",
//     instance: "/account/12345/msgs/abc",
//     balance: 30,
//     accounts: ["/account/12345", "/account/67890"]
//   }

b.problemDetails.fromError(err, opts?) #

stable0.8.84
{
  title:    string,   // override the derived title
  instance: string,   // request-instance URI reference
  status:   number,   // override err.statusCode / default 500
}

Convert a thrown FrameworkError (or any error with a code field) into the matching problem-details object. The error's code (e.g. csv/invalid-record) becomes the type-URI suffix (/csv/invalid-record); the error's message becomes detail; the error's statusCode becomes status when present, otherwise defaults to 500. Pass opts.title to override the title default (which is the error class name humanized: CsvError → "CSV Error"). Pass opts.instance to attach a request-instance reference (typically the audit-trail ID).

try {
  b.csv.parse(badInput);
} catch (err) {
  var problem = b.problemDetails.fromError(err, {
    instance: "/audit/" + req.auditId,
  });
  b.problemDetails.respond(res, problem);
}

b.problemDetails.respond(res, problem, req?) #

stable0.8.84

Write a problem-details object to the response with the correct RFC 9457 §3 content type (application/problem+json). Sets Cache-Control: no-store (RFC 9111 §5.2.2.5 — error responses are individualized) and writes the JSON body. Status code is taken from problem.status (or 500 when missing). Throws problem-details/bad-res for non-response objects; throws problem-details/bad-problem for non-object problem inputs.

var problem = b.problemDetails.create({
  type:   "https://blamejs.com/problems/csv/invalid-record",
  title:  "CSV record validation failed",
  status: 400,
  detail: "Row 3 column 5 has an unterminated quoted field",
});
b.problemDetails.respond(res, problem);
// res.headers:  Content-Type: application/problem+json
//               Cache-Control: no-store
// res.body:     
// res.statusCode: 400

b.problemDetails.send(res, fields) #

stable0.9.41
{
  status:    number,           // HTTP status code (100..599); default 500
  title:     string,           // operator-supplied short title
  detail:    string,           // operator-supplied human-readable explanation
  type:      string,           // problem-type URI (defaults to "about:blank")
  instance:  string,           // optional per-occurrence URI
  extensions: object,          // keys spread as top-level siblings (§3.2); direct top-level key wins on collision
}

Build + emit a problem-details response in one call. Equivalent to respond(res, create(fields)) but lets routes migrate incrementally from inline res.status(400).json({ error: "..." }) shapes without restructuring the handler around an error throw.

The same RFC 9457 §3 application/problem+json content type + Cache-Control: no-store are written; status code defaults to 500 when omitted.

extensions keys are spread as top-level sibling members (RFC 9457 §3.2) via create — the literal extensions member is never emitted. Keys colliding with the reserved type / title / status / detail / instance are ignored; prototype-pollution- shaped keys are dropped. A direct top-level key wins over the same name nested under extensions.

// Migrating from inline JSON-error shape:
//   res.status(400).json({ error: "Missing 'name' field" });
// to RFC 9457 problem-details:
b.problemDetails.send(res, {
  status: 400,
  title:  "Missing required field",
  detail: "Body field 'name' is required",
});

b.problemDetails.validate(doc) #

stable0.8.84

Validate an INBOUND problem-details document (e.g. one received from an upstream API). Returns the doc unchanged on success; throws problem-details/bad-inbound on shape violations. Useful when blamejs is the client of a RFC 9457-compliant upstream service — converts a "looks JSON-ish" response into a verified problem object before reading fields.

- Refuses non-object input. - Refuses status outside 100..599 or non-integer. - Refuses type / title / detail / instance of non-string shape when present. - Refuses prototype-pollution-shaped extension keys.

var rsp = await fetch(url);
if (rsp.headers.get("content-type") === "application/problem+json") {
  var doc = b.problemDetails.validate(await rsp.json());
  console.log(doc.title, doc.status, doc.detail);
}

Last updated 2026-08-08T16:39:49.652Z by seeder.