# `ALLM.Error.ModerationAdapterError`
[🔗](https://github.com/cykod/ALLM/blob/v0.6.1/lib/allm/error/moderation_adapter_error.ex#L1)

Errors returned by `ALLM.ModerationAdapter` implementations.

Layer A — serializable (no PIDs, refs, funs, or raw API keys). Closed-enum
exception struct carrying the same eleven reasons as
`ALLM.Error.EmbeddingAdapterError`.

Two atoms that appear on `ALLM.Error.ImageAdapterError` are deliberately
absent. There is no `:content_filter`: a moderation call is never itself
content-filtered — classifying harmful text is the endpoint's whole
purpose. There is no `:unsupported_operation` either, for the same reason
it is absent from the embeddings sibling — moderation has no operations
enum.

## Error reasons

| Reason | HTTP status | Fires when |
|--------|-------------|------------|
| `:authentication_failed` | 401/403 | API key missing or invalid. Surface to the user; no retry. |
| `:rate_limited` | 429 | Provider quota exceeded; `:retry_after_ms` populated when a `Retry-After` header is present. Retried automatically. |
| `:invalid_request` | 400 | Request shape rejected, an empty `:input` reaching a direct adapter call, an unsupported image type, or an image over the provider's size cap. `:metadata` carries the specific detail. Fix the request; no retry. |
| `:context_length_exceeded` | 400 | A single input exceeds the model's token limit. Shorten it; no retry. |
| `:provider_unavailable` | 5xx | Provider server-side failure. Retried automatically. |
| `:timeout` | — | Adapter `request_timeout` exceeded. Retried automatically. |
| `:network_error` | — | TCP/TLS/DNS failure. Retried automatically. |
| `:malformed_response` | — | 200 with an unparseable body, or a results entry missing its verdict. No retry; file a bug. |
| `:unsupported_feature` | — | Request used a field this adapter cannot express; `metadata.feature` names it. No retry. |
| `:batch_too_large` | — | the request's item count exceeds `c:ALLM.ModerationAdapter.max_batch_size/0`; `metadata` carries `:count` and `:max`. `ALLM.moderate/3` does NOT chunk, so this IS reachable through the façade — recover by chunking the input yourself. |
| `:unknown` | any | Catch-all for shapes the adapter cannot classify; non-retryable. |

# `reason`

```elixir
@type reason() ::
  :authentication_failed
  | :rate_limited
  | :invalid_request
  | :context_length_exceeded
  | :provider_unavailable
  | :timeout
  | :network_error
  | :malformed_response
  | :unsupported_feature
  | :batch_too_large
  | :unknown
```

Closed set of moderation-adapter error reasons.

# `t`

```elixir
@type t() :: %ALLM.Error.ModerationAdapterError{
  __exception__: true,
  cause: term() | nil,
  message: String.t(),
  metadata: map(),
  provider: atom() | nil,
  reason: reason(),
  retry_after_ms: non_neg_integer() | nil,
  status: pos_integer() | nil
}
```

# `legal_reasons`

```elixir
@spec legal_reasons() :: [reason()]
```

Return the closed list of legal `:reason` atoms.

## Examples

    iex> :batch_too_large in ALLM.Error.ModerationAdapterError.legal_reasons()
    true

    iex> length(ALLM.Error.ModerationAdapterError.legal_reasons())
    11

# `new`

```elixir
@spec new(
  reason(),
  keyword()
) :: t()
```

Build a `%ModerationAdapterError{}` from a `reason` atom and optional
keyword fields.

`opts` may include `:message`, `:provider`, `:status`, `:retry_after_ms`,
`:cause`, and `:metadata`. When `:message` is omitted, the default is
`"moderation adapter error: #{reason}"` — with a provider suffix
`"moderation adapter error (#{provider}): #{reason}"` when `:provider`
is set.

Raises `ArgumentError` if `reason` is not one of the atoms in the closed
`t:reason/0` enum.

## Examples

    iex> err = ALLM.Error.ModerationAdapterError.new(:timeout)
    iex> err.reason
    :timeout
    iex> Exception.message(err)
    "moderation adapter error: timeout"

    iex> err = ALLM.Error.ModerationAdapterError.new(:batch_too_large, metadata: %{count: 500, max: 32})
    iex> err.metadata.max
    32

---

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