# `PhoenixKit.API.Problem`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.64.1/lib/phoenix_kit/api/problem.ex#L1)

RFC 9457 `application/problem+json` errors for the machine API.

Every error the API sends, from the endpoint's `PhoenixKit.API.BodyGuard`
to a tool handler's `{:error, code, message, context}`, goes through
`send/4`, so a client sees one shape:

    {"type": "https://phoenixkit.dev/api/errors/forbidden",
     "title": "Forbidden", "status": 403, "code": "forbidden",
     "detail": "This key lacks catalogue.prices:write.",
     "context": {"required_scope": "catalogue.prices:write"},
     "request_id": "F9x..."}

`code` is the contract: stable, machine-readable, and what an agent
branches on. `detail` is always English prose for a human or an agent to
relay. `context` carries the fields that make the error actionable
(`required_scope`, `errors`, `retry_after`, ...).

The codes of core are a closed table here (`core_code?/1`). A module adds its
own through `c:PhoenixKit.Module.api_problems/0` (it can never replace a code of
core), and `PhoenixKit.API.Legacy` supplies the few codes the API had
before they became a module's, for as long as no enabled module declares them.
Both are read only here, when an error is answered, so a successful call pays
nothing. `statuses/0` is the table as it stands right now. A code that is in
none of the three answers `500` and is logged: it means a handler returned a
code nothing declares.

# `body`

```elixir
@spec body(atom(), String.t(), map(), String.t() | nil) :: map()
```

The problem document as a map with string keys.

`request_id` is left out of the map when it is `nil`.

# `core_code?`

```elixir
@spec core_code?(atom()) :: boolean()
```

Whether `code` is one of core's own, which no module can override.

# `declared`

```elixir
@spec declared(atom()) ::
  %{status: pos_integer(), title: String.t(), advice: String.t() | nil} | nil
```

What is declared for a `code` that core does not define: `%{status, title, advice}`
from the enabled module that declares it, else from `PhoenixKit.API.Legacy`, else
`nil`. A module's declaration replaces the legacy one as a whole.

# `send`

```elixir
@spec send(Plug.Conn.t(), atom(), String.t(), map()) :: Plug.Conn.t()
```

Sends the problem document for `code` and halts the connection.

The response carries `content-type: application/problem+json` and
`cache-control: no-store`. Three codes add a header: `unauthorized` a
`www-authenticate` challenge, `forbidden` with a `required_scope` in the
context an `insufficient_scope` challenge, and any context with a
`retry_after` (seconds) a `retry-after` header.

The request id is `conn.assigns.phoenix_kit_api_request_id` when the
`PhoenixKit.API.Auth` plug has run, else the `x-request-id` response
header that `Plug.RequestId` set. A header value with characters outside
`[A-Za-z0-9_=+/-]` (`Plug.RequestId` accepts any client-sent id of 20..200
bytes) is replaced by a fresh id, in the header and in the body, the same
rule `Auth` applies.

# `status`

```elixir
@spec status(atom()) :: pos_integer()
```

The HTTP status of an error `code`.

A code that no table declares answers `500`.

# `statuses`

```elixir
@spec statuses() :: %{required(atom()) =&gt; pos_integer()}
```

Every error code the API can answer with right now, with its HTTP status: core's,
those of the enabled modules and the `PhoenixKit.API.Legacy` ones no module
declares.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
