# Problem Details

> DbError as RFC 9457 application/problem+json, with RFC 6750 challenges.

Source: https://bettersupabase.com/docs/auth/problems

Server adapters answer errors with [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)
Problem Details. The `DbError` fields become extension members, so clients
keep the structured error:

```http
HTTP/1.1 409 Conflict
Content-Type: application/problem+json

{
  "type": "https://bettersupabase.com/problems/conflict",
  "title": "Conflict",
  "status": 409,
  "kind": "conflict",
  "detail": "duplicate key value violates unique constraint …",
  "instance": "/customers",
  "code": "23505",
  "constraint": "customers_organization_id_kvk_key",
  "columns": ["organization_id", "kvk"]
}
```

```ts
import { fromProblem, problemResponse } from "better-supabase";

if (!result.ok)
  return problemResponse(result.error, { instance: url.pathname });

// client side
const error = fromProblem(await response.json()); // back to a DbError
```

Kind-specific details become extension members next to `code`: `constraint`
and `columns` for conflicts, `column` for `not_null`, and `required` (`aal1` or
`aal2`) when a guard's [`aal`](/docs/auth/mfa-sso) rejects the session,
`scopes` when a guard's `scopes` rejects a delegated token
(`code: "INSUFFICIENT_SCOPE"`), `code: "ANONYMOUS_USER"` when a guard turns
away an anonymous user because `allow` lacks `'anonymous'`, `retryAfter` for `rate_limited`, which
also sets the `Retry-After` header, `meter`, `limit` and `retryAfter` for
`quota_exceeded`, and `maxAffected` for `max_affected`.

When an [authorizer](/docs/extending/authorizers) refuses a declared
permission, the `forbidden` error carries `permission` (the permission key)
and one of two codes. `PERMISSION_DENIED` means the authorizer denied it,
failed, or isn't set; `detail` holds its reason. `APPROVAL_REQUIRED` means
the action needs an approval first, and `approval` (`{ "id": "..." }`) names
the pending approval when the authorizer gives one:

```json
{
  "type": "https://bettersupabase.com/problems/forbidden",
  "title": "Forbidden",
  "status": 403,
  "kind": "forbidden",
  "detail": "invoices.update needs an approval",
  "code": "APPROVAL_REQUIRED",
  "permission": "invoices.update",
  "approval": { "id": "apr_9" }
}
```

## Safe by default [#safe-by-default]

Messages of `unexpected`, `network` and `invalid_request` errors can contain
SQL or hostnames, so they are left out unless you pass `expose: true` (for
example in development). Validation errors keep their `issues`.

## Your own error format [#your-own-error-format]

`format` rewrites the body, for apps whose errors use their own `type` URIs
or codes. It gets the Problem Details and the `DbError`; the status and the
`Retry-After` and `WWW-Authenticate` headers stay as they are.

```ts
const toAppError = (problem: ProblemDetails, error: DbError) => ({
  type: `https://errors.example.com/${error.code ?? problem.kind}`,
  title: problem.title,
  status: problem.status,
  detail: problem.detail,
});

problemResponse(error, { instance, format: toAppError });
```

The blocks that answer HTTP requests take the same function as `problem`:
`createIdempotency`, `createWebhookInbox`, `createIncomingWebhooks`, `rateLimited`,
and the `drainRoute`, `deliverRoute` and `relayRoute` options.

## 401 responses [#401-responses]

A 401 carries an [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750)
challenge: `WWW-Authenticate: Bearer realm="supabase", error="invalid_token"`,
without `error` when no credentials were sent. Set the realm with `realm`.

A 403 for a missing OAuth scope carries the RFC 6750 `insufficient_scope`
challenge, naming the scopes the route needs:
`WWW-Authenticate: Bearer realm="supabase", error="insufficient_scope", scope="customers:read"`.