A provider (LLM or classifier) answered with a non-success status (ADR 0027 / D5).
The failure is carried as a VALUE — status, body, retry_after — so a host can
decide what to log without parsing a sentence. The message is the redacted,
capped rendering; body keeps the response intact for a host that genuinely wants it.
Redaction policy, identical to the classifier's and now shared with the §8 client path:
401/403bodies are never echoed (a gateway reflects the Authorization header)- known account-identifier keys —
user_id,account_id,org_id,organization— are replaced with«redacted», never dropped, so the shape of the body survives - the rendered cause is capped at 200 bytes, with an ellipsis
A cap is NOT redaction: the cap runs LAST, after the keys are replaced.
retry_after is the raw Retry-After header verbatim, or nil when the response
did not send one. It is deliberately NOT pre-parsed: an HTTP-date, fractional or
out-of-range value is information the response really supplied, and a numeric field
would have to null it out. The library's own waiting rule (delay-seconds only,
falling back to backoff) is separate and unchanged.
Summary
Functions
The account-identifier keys replaced with «redacted» before interpolation.
Render an ALREADY-REDACTED body for an error message: blank on 401/403, then
capped at 200 bytes (message-only). Returns "" or ": " <> text.
Replace every known account-identifier value with «redacted», at any depth.
Structured bodies are walked; a string body is rewritten with a key-aware regex so
a JSON blob that never reached a decoder is redacted too.
The replacement token. Kept as a constant so every port spells it identically.
Types
@type t() :: %Toolnexus.ProviderError{ __exception__: true, body: term(), message: String.t(), retry_after: String.t() | nil, status: non_neg_integer() | nil }
Functions
@spec account_keys() :: [String.t()]
The account-identifier keys replaced with «redacted» before interpolation.
@spec cause(non_neg_integer() | nil, term()) :: String.t()
Render an ALREADY-REDACTED body for an error message: blank on 401/403, then
capped at 200 bytes (message-only). Returns "" or ": " <> text.
Replace every known account-identifier value with «redacted», at any depth.
Structured bodies are walked; a string body is rewritten with a key-aware regex so
a JSON blob that never reached a decoder is redacted too.
@spec redacted() :: String.t()
The replacement token. Kept as a constant so every port spells it identically.