# List users (admin)

`GET /v1/admin/orgs/{slug}/users`

Returns one page of local users using opaque cursor (keyset) pagination.
Bound the page size with `limit` and choose an ordering with `sort` (see
`x-sort-fields` for what this endpoint accepts; the user id is appended so
the ordering is total). Then walk the listing by passing the previous
response's `page.next_cursor` back as `cursor` for as long as
`page.has_more` is true; `page.prev_cursor` steps back the same way.
Defaults to id order, which for this server's time-ordered ids is creation
order. Requires the `nauthera.users:read` scope and the `users.read`
permission; the listing is confined to `slug`'s organization. Password
hashes are never returned.

`q` narrows the listing to the users one of the `match` fields of whom
starts with it, case-insensitively; every character is matched
literally. The username is a field every caller may match on. The
display name and the email are personal data: only a caller holding the
`users.pii.read` permission may match on them, since a prefix search
would rebuild them one character at a time. Without `match` a search
matches every field the caller may match on; a `match` naming a field
the caller may not is refused with 403 rather than answered from the
others. A cursor is bound to the search and the fields it was issued
for, so send them with it; the order it carries itself.

The filters narrow the listing further and compose as a conjunction; a
filter taking several values matches any of them. `status` and `locked`
are decided at the moment of the request: a suspension whose end has
passed counts as `active`, and a lock whose deadline has passed does not
count. The time ranges include their start and exclude their end. A user
who never signed in has signed in before any instant, so
`last_sign_in_to` keeps them and `last_sign_in_from` does not. A cursor
is bound to the filters it was issued for as it is to the search.

`last_login_at` sorts users who never signed in before everyone else
ascending and after everyone else descending, among themselves by id.

## Authorization

- [Bearer token](https://www.nauthera.io/docs/v1/api#auth-bearerAuth) (`bearerAuth`) and [OAuth 2.0](https://www.nauthera.io/docs/v1/api#auth-oauth2) (`oauth2`) with scope `nauthera.users:read`

Authorization header: The access token, as `Bearer <token>` (RFC 6750 §2.1) or, for a sender-constrained token, `DPoP <token>` (RFC 9449 §7.1). A token minted with a `cnf.jkt` confirmation is only accepted together with a `DPoP` proof signed by that key, whichever of the two schemes carries it.

Requires the permission [`users.read`](https://www.nauthera.io/docs/v1/api#permission-users.read): Read an organization's users.

This endpoint is rate limited; it answers `429` when a limit is hit.

## Path parameters

- `slug` (string, required): The organization slug. It names the TENANT OF RECORD for the operation: the rows it reads and writes, the policy its secrets are derived under, and the organization its audit record is filed against all come from this organization. A token is admitted only through its own slug, except a main-org token, which may be admitted through any — so cross-tenant administration is performed through the TARGET tenant's slug, never by naming its resources through another. A token reaching for a slug it may not be admitted through is refused identically whether that organization exists or not. Constraints: 1–64 characters.

## Query parameters

- `limit` (integer, optional): Maximum number of items to return in one page (1–200; default 50). A value outside that range is rejected with a 400. It may be changed freely between pages of the same walk — a cursor marks a position, not a page number. Default: `50`. Constraints: between 1 and 200.
- `sort` (string, optional): Ordering for the listing: a comma-separated list of fields, most significant first, a leading `-` marking a descending term. For example `sort=last_name,-first_name` sorts by last name ascending, then by first name descending. Constraints: at most 256 characters; pattern `^-?[a-z][a-z0-9_]*(,-?[a-z][a-z0-9_]*)*$`. Sortable fields: `created_at`, `email`, `id`, `last_login_at`, `username`. Prefix a field with `-` to sort descending.

  Which fields a given endpoint accepts is listed in its `x-sort-fields`
  extension; naming anything else is a 400. A field may appear only once.

  The server always appends the resource's primary key as a final term, so
  the ordering is TOTAL — rows that tie on every field you named still have
  a defined relative order, without which a page could skip or repeat them.
  The full ordering, including that appended term, is echoed as `page.sort`.

  Send it on the FIRST request of a walk only: every cursor records the
  ordering it was issued under, so it carries forward automatically, and a
  `sort` that contradicts the supplied `cursor` is rejected with a 400
  rather than silently reordering mid-walk.
- `cursor` (string, optional): Opaque keyset cursor identifying where a page begins. Omit it for the first page; afterwards pass the `next_cursor` (or `prev_cursor`) value from the previous response verbatim — the token itself records which direction it steps in, so there is no separate direction parameter. Constraints: at most 1024 characters.

  The token is server-defined and MUST be treated as opaque: it is bound
  to the listing and sort order that issued it, and a cursor that is
  malformed, tampered with, or belongs to a different listing or order is
  rejected with a 400 rather than silently paging through the wrong set.
- `q` (string, optional): Narrow the listing to the users one of whose `match` fields starts with this text, compared case-insensitively. Every character is matched literally. A cursor is bound to the search it was issued for. Constraints: 2–100 characters.
- `match` (array of enums, optional): The fields `q` matches on, comma-separated. Defaults to every field the caller may match on; `display_name` and `email` need the `users.pii.read` permission. Ignored without `q`. One of: `"username"`, `"display_name"`, `"email"`. Constraints: at most 3 items.
- `status` (array of enums, optional): Account statuses in force now, comma-separated: a user whose status is any of them matches. One of: `"active"`, `"suspended"`, `"blocked"`. Constraints: at most 3 items.
- `locked` (boolean, optional): Only users the automatic lock after repeated wrong passwords refuses now (true), or only users it does not (false).
- `second_factor` (boolean, optional): Only users with an active second factor (true), or only users without one (false).
- `email_verified` (boolean, optional): Only users whose address is verified (true), or only users whose address is not (false), as a user who signed up and never followed the verification link.
- `created_from` (string (date-time), optional): Only users created at or after this instant (RFC3339).
- `created_to` (string (date-time), optional): Only users created before this instant (RFC3339).
- `last_sign_in_from` (string (date-time), optional): Only users whose last sign-in was at or after this instant (RFC3339). Users who never signed in do not match.
- `last_sign_in_to` (string (date-time), optional): Only users whose last sign-in was before this instant (RFC3339), including users who never signed in.

## Headers

- `DPoP` (string, optional): DPoP proof JWT (RFC 9449 §4). REQUIRED on a request presenting a DPoP-bound access token, and it must be signed by the key named in the token's `cnf.jkt` and cover this request's method and URI. The proof is read from the request itself rather than from this decoded parameter, so every protected operation is covered whether or not it declares it.

## Responses

### 200 OK

One page of users.

Content type: `application/json`

Headers:
- `Cache-Control` = `no-store`

One page of users under cursor (keyset) pagination: `items` holds the
page, ordered by the request's `sort`, and `page` says where it sits in
the listing. Follow `page.next_cursor` while `page.has_more` is true;
there is no total to count towards.

- `items` (array of objects, required): The users on this page.

  Each item: A local user as seen by the admin API. The argon2id password hash is NEVER included. `attributes` is the free-form string map stored on the identity (e.g. the display "name"). The nullable timestamps (locked_until, last_login_at) are omitted when unset. Admin access is not a property of the user: it follows the roles the user is granted in an organization (e.g. org-user-admin), which are resolved on every admin request.
  - `id` (string, required): The user id (UUID). Example: `"3f1a5b2c-8d4e-4f6a-9b0c-1d2e3f4a5b6c"`.
  - `username` (string, required): Example: `"alice@example.com"`.
  - `email` (string, required): Example: `"alice@example.com"`.
  - `second_factors` (array of enums, required): The methods of the user's active second factors, in method order; empty when they hold none. A factor still being enrolled is not one. One of: `"totp"`, `"passkey"`, `"email"`. Constraints: at most 3 items.
  - `password_set` (boolean, required): Whether the user has a password. An invited user has none until they accept the invitation, and an account an identity provider created has none at all.
  - `password_change_required` (boolean, required): Whether the user's next password sign-in, once its password and any second factor are proven, ends on the page that sets a new password instead of in a session. It clears when the user sets one.
  - `invitation_pending` (boolean, required): Whether the user still waits on an invitation: they have no password and no linked identity provider account to sign in with.
  - `failed_login_attempts` (integer, required): Consecutive failed password verifications since the last success.
  - `status` (enum, required): What an administrator decided about an account, separate from the automatic lock after repeated wrong passwords. `active` may sign in; `suspended` may not until it is reactivated or its `suspended_until` passes, after which it reads `active` again with no write in between; `blocked` may not until it is reactivated. A suspended or blocked account can neither sign in nor obtain or renew a token through any grant. Translated under `vocabulary.UserAccountStatus.<value>`. **Compatibility:** closed. A client should render a value it does not know as unavailable rather than active. One of: `"active"`, `"suspended"`, `"blocked"`.
  - `created_at` (string (date-time), required)
  - `updated_at` (string (date-time), required)
  - `email_verified_at` (string (date-time), optional): When `email` was proven to be the user's (RFC3339); omitted while it has not been. It is released as the `email_verified` claim, and a change of address clears it.
  - `attributes` (map of strings, optional): Free-form string attributes stored on the identity.
  - `locked_until` (string (date-time), optional): Soft-lock deadline (RFC3339); omitted when the account is not locked.
  - `last_login_at` (string (date-time), optional): Last successful sign-in (RFC3339); omitted when the user has never signed in.
  - `status_reason` (string, optional): The administrator's note on the current status; omitted when there is none.
  - `suspended_until` (string (date-time), optional): When a suspension lifts by itself (RFC3339); omitted when the account is not suspended or the suspension has no end.
  - `status_changed_at` (string (date-time), optional): When the status last changed (RFC3339); omitted when it never has.
- `page` (object, required): Where one page sits in a cursor (keyset) paginated list, returned beside that page's `items`.

  Walk a list by following `next_cursor` for as long as `has_more` is true,
  passing the token back verbatim as the `cursor` query parameter. Keep the
  listing and its filters the same for the whole walk; `limit` MAY change
  between pages (a cursor marks a position, not a page number), and `sort`
  travels in the cursor so it need not be resent. `has_more` is
  authoritative: it is derived from whether a further row actually exists,
  so a page that happens to be full is not mistaken for a page that has a
  successor, and a walk never costs a trailing empty request. `prev_cursor`
  steps back the same way.

  A cursor is opaque — server-defined, bound to this listing and sort
  order, and not parseable by clients. It stays valid across page-size
  changes but not across a change of sort order or filters; a cursor that
  does not belong to the request is rejected with a 400 rather than
  silently paging through the wrong set.

  Keyset paging is stable under concurrent writes, with one qualification.
  A row whose SORT KEY does not change is returned exactly once for the
  whole walk. Rows created or deleted mid-walk may or may not appear,
  depending on which side of the cursor they fall — and so may a row whose
  sort key is UPDATED mid-walk, since that moves it across the boundary: it
  can be seen twice or missed. Sorting by a field that does not change (the
  default id ordering, or `created_at`) avoids this entirely.
  - `limit` (integer, required): The page size in effect: the `limit` that was sent, or the server's default when it was omitted. Echoed so a client resuming from a stored cursor knows the page size without having to remember it. Example: `50`.
  - `sort` (string, required): The ordering this walk is pinned to, in the `sort` parameter's syntax and INCLUDING the primary-key term the server appends to make it total. Cursors carry it, so it need only be sent on the first request. Example: `"last_name,-first_name,id"`.
  - `has_more` (boolean, required): Whether a page follows this one and `next_cursor` can reach it. When false this is the last page and `next_cursor` is absent.
  - `has_prev` (boolean, required): Whether a page precedes this one and `prev_cursor` can reach it. False on the first page of a walk.

    Both flags report REACHABILITY, not bare existence. A cursor that overshoots the set — a stale bookmark, or rows deleted mid-walk — returns an empty page with both flags false even though rows precede it, because an empty page has no row to anchor a cursor on. Restart the walk if you land there; a client following `has_more` never does.
  - `next_cursor` (string, optional): Opaque cursor for the next page. Present exactly when `has_more` is true, and ABSENT (the field is omitted, not null) otherwise. Pass it back verbatim as the `cursor` query parameter; never parse it. Constraints: at most 512 characters.
  - `prev_cursor` (string, optional): Opaque cursor for the previous page. Present exactly when `has_prev` is true, and absent otherwise. Passed back as `cursor`, exactly like `next_cursor` — the token itself records which way it steps. Constraints: at most 512 characters.

### 400 Bad Request

The pagination parameters or the search were invalid — a malformed or foreign cursor, a cursor contradicting `sort`, a `sort` naming a field this endpoint does not accept or naming one twice, a `limit` outside its declared range, a `q` outside its declared length, a `match` or `status` value outside its vocabulary, a malformed instant, or a range whose start is not before its end.

Content type: `application/problem+json`

RFC 9457 (formerly RFC 7807) problem details (application/problem+json). `code` is a stable machine-readable slug for the problem kind (it mirrors the last path segment of `type`), so consumers can branch on it without parsing prose. `type` is `https://nauthera.io/problems/<code>`, which resolves to the code's runbook: why it happened, who is affected and what to do (RFC 9457 §3.1.1). A problem without a code has `about:blank`. `errors` is present on validation failures and lists every offending field.

- `title` (string, required): Example: `"Request validation failed"`.
- `status` (integer, required): Example: `400`.
- `type` (string (uri), optional): Example: `"https://nauthera.io/problems/validation_error"`.
- `detail` (string, optional): Example: `"One or more fields did not satisfy the API schema."`.
- `code` (enum, optional): The kind of problem, as a stable machine-readable slug a client branches on and translates rather than parsing `title` or `detail`, which are English prose for logs. A problem whose status says everything the server knows carries no `code`. One of: `"invalid_request"`, `"invalid_credentials"`, `"forbidden"`, `"unauthorized"`, `"insufficient_user_authentication"`, `"protected_account"`, `"password_held_by_directory"`, `"last_role_administrator"`, `"invalid_code"`, `"too_many_requests"`, `"too_many_attempts"`, `"account_locked"`, `"account_unavailable"`, `"already_enrolled"`, `"registration_disabled"`, `"registration_conflict"`, `"captcha_failed"`, `"invalid_reset_link"`, `"password_reused"`, `"password_too_short"`, `"password_too_long"`, `"password_compromised"`, `"invalid_verification_link"`, `"invalid_invitation_link"`, `"mail_relay_missing"`, `"email_unverified"`, `"not_found"`, `"conflict"`, `"unavailable"`, `"busy"`, `"window_too_large"`, `"too_many_series"`, `"internal_error"`, `"rate_limited"`, `"malformed"`, `"body_too_large"`, `"required"`, `"validation_error"`, `"unknown_org"`, `"invalid_token"`, `"invalid_dpop_proof"`, `"use_dpop_nonce"`, `"server_error"`.

  `validation_error` is the one code whose message is not a sentence about
  the code itself: the request failed the schema, and the specifics are
  the `errors` list — one entry per offending field — which a form renders
  on those fields. `required` and `malformed` are spelled like two of the
  per-field violation codes (`FieldError.code`) but mean something else;
  the two vocabularies are told apart by position, never by spelling.

  **Compatibility:** this is a CLOSED enum, with the same cost as
  `AuditAction`: a client generated against an older version of this
  document rejects a problem carrying a code added after it was built.
  Regenerate on upgrade, or decode the field as a plain string. It is
  enumerated anyway so that what a client must translate is knowable
  from the contract alone.
- `instance` (string, optional): Example: `"/introspect"`.
- `request_id` (string, optional): Example: `"8d94ed49-1caf-4ab3-9594-557142f2d023"`.
- `trace_id` (string, optional): Distributed-trace id (hex) for correlating this response with traces and logs. Present only when tracing is enabled and the request was recorded. Opaque and safe to log/share. Example: `"4bf92f3577b34da6a3ce929d0e0e4736"`.
- `errors` (array of objects, optional): Field-level violations (present for validation errors).

  Each item: A single field-level validation violation.
  - `field` (string, required): Offending field, dotted for nested bodies (e.g. child.id). Example: `"token_type_hint"`.
  - `code` (string, required): Machine-readable violation code: required, too_short, too_long, pattern, invalid_format, not_allowed, minimum, maximum, multiple_of, too_few, too_many, not_unique, malformed. Example: `"not_allowed"`.
  - `message` (string, required): Example: `"field token_type_hint must be one of [access_token, refresh_token]"`.

### 401 Unauthorized

Problem details (RFC 9457) with an RFC 6750 / RFC 9449 challenge.

Content type: `application/problem+json`

Headers:
- `Cache-Control` = `no-store`
- `WWW-Authenticate` (string)
- `DPoP-Nonce` (string)

RFC 9457 (formerly RFC 7807) problem details (application/problem+json). `code` is a stable machine-readable slug for the problem kind (it mirrors the last path segment of `type`), so consumers can branch on it without parsing prose. `type` is `https://nauthera.io/problems/<code>`, which resolves to the code's runbook: why it happened, who is affected and what to do (RFC 9457 §3.1.1). A problem without a code has `about:blank`. `errors` is present on validation failures and lists every offending field.

- `title` (string, required): Example: `"Request validation failed"`.
- `status` (integer, required): Example: `400`.
- `type` (string (uri), optional): Example: `"https://nauthera.io/problems/validation_error"`.
- `detail` (string, optional): Example: `"One or more fields did not satisfy the API schema."`.
- `code` (enum, optional): The kind of problem, as a stable machine-readable slug a client branches on and translates rather than parsing `title` or `detail`, which are English prose for logs. A problem whose status says everything the server knows carries no `code`. One of: `"invalid_request"`, `"invalid_credentials"`, `"forbidden"`, `"unauthorized"`, `"insufficient_user_authentication"`, `"protected_account"`, `"password_held_by_directory"`, `"last_role_administrator"`, `"invalid_code"`, `"too_many_requests"`, `"too_many_attempts"`, `"account_locked"`, `"account_unavailable"`, `"already_enrolled"`, `"registration_disabled"`, `"registration_conflict"`, `"captcha_failed"`, `"invalid_reset_link"`, `"password_reused"`, `"password_too_short"`, `"password_too_long"`, `"password_compromised"`, `"invalid_verification_link"`, `"invalid_invitation_link"`, `"mail_relay_missing"`, `"email_unverified"`, `"not_found"`, `"conflict"`, `"unavailable"`, `"busy"`, `"window_too_large"`, `"too_many_series"`, `"internal_error"`, `"rate_limited"`, `"malformed"`, `"body_too_large"`, `"required"`, `"validation_error"`, `"unknown_org"`, `"invalid_token"`, `"invalid_dpop_proof"`, `"use_dpop_nonce"`, `"server_error"`.

  `validation_error` is the one code whose message is not a sentence about
  the code itself: the request failed the schema, and the specifics are
  the `errors` list — one entry per offending field — which a form renders
  on those fields. `required` and `malformed` are spelled like two of the
  per-field violation codes (`FieldError.code`) but mean something else;
  the two vocabularies are told apart by position, never by spelling.

  **Compatibility:** this is a CLOSED enum, with the same cost as
  `AuditAction`: a client generated against an older version of this
  document rejects a problem carrying a code added after it was built.
  Regenerate on upgrade, or decode the field as a plain string. It is
  enumerated anyway so that what a client must translate is knowable
  from the contract alone.
- `instance` (string, optional): Example: `"/introspect"`.
- `request_id` (string, optional): Example: `"8d94ed49-1caf-4ab3-9594-557142f2d023"`.
- `trace_id` (string, optional): Distributed-trace id (hex) for correlating this response with traces and logs. Present only when tracing is enabled and the request was recorded. Opaque and safe to log/share. Example: `"4bf92f3577b34da6a3ce929d0e0e4736"`.
- `errors` (array of objects, optional): Field-level violations (present for validation errors).

  Each item: A single field-level validation violation.
  - `field` (string, required): Offending field, dotted for nested bodies (e.g. child.id). Example: `"token_type_hint"`.
  - `code` (string, required): Machine-readable violation code: required, too_short, too_long, pattern, invalid_format, not_allowed, minimum, maximum, multiple_of, too_few, too_many, not_unique, malformed. Example: `"not_allowed"`.
  - `message` (string, required): Example: `"field token_type_hint must be one of [access_token, refresh_token]"`.

### 403 Forbidden

Problem details (RFC 9457) with an RFC 6750 / RFC 9449 challenge.

Content type: `application/problem+json`

Headers:
- `Cache-Control` = `no-store`
- `WWW-Authenticate` (string)
- `DPoP-Nonce` (string)

RFC 9457 (formerly RFC 7807) problem details (application/problem+json). `code` is a stable machine-readable slug for the problem kind (it mirrors the last path segment of `type`), so consumers can branch on it without parsing prose. `type` is `https://nauthera.io/problems/<code>`, which resolves to the code's runbook: why it happened, who is affected and what to do (RFC 9457 §3.1.1). A problem without a code has `about:blank`. `errors` is present on validation failures and lists every offending field.

- `title` (string, required): Example: `"Request validation failed"`.
- `status` (integer, required): Example: `400`.
- `type` (string (uri), optional): Example: `"https://nauthera.io/problems/validation_error"`.
- `detail` (string, optional): Example: `"One or more fields did not satisfy the API schema."`.
- `code` (enum, optional): The kind of problem, as a stable machine-readable slug a client branches on and translates rather than parsing `title` or `detail`, which are English prose for logs. A problem whose status says everything the server knows carries no `code`. One of: `"invalid_request"`, `"invalid_credentials"`, `"forbidden"`, `"unauthorized"`, `"insufficient_user_authentication"`, `"protected_account"`, `"password_held_by_directory"`, `"last_role_administrator"`, `"invalid_code"`, `"too_many_requests"`, `"too_many_attempts"`, `"account_locked"`, `"account_unavailable"`, `"already_enrolled"`, `"registration_disabled"`, `"registration_conflict"`, `"captcha_failed"`, `"invalid_reset_link"`, `"password_reused"`, `"password_too_short"`, `"password_too_long"`, `"password_compromised"`, `"invalid_verification_link"`, `"invalid_invitation_link"`, `"mail_relay_missing"`, `"email_unverified"`, `"not_found"`, `"conflict"`, `"unavailable"`, `"busy"`, `"window_too_large"`, `"too_many_series"`, `"internal_error"`, `"rate_limited"`, `"malformed"`, `"body_too_large"`, `"required"`, `"validation_error"`, `"unknown_org"`, `"invalid_token"`, `"invalid_dpop_proof"`, `"use_dpop_nonce"`, `"server_error"`.

  `validation_error` is the one code whose message is not a sentence about
  the code itself: the request failed the schema, and the specifics are
  the `errors` list — one entry per offending field — which a form renders
  on those fields. `required` and `malformed` are spelled like two of the
  per-field violation codes (`FieldError.code`) but mean something else;
  the two vocabularies are told apart by position, never by spelling.

  **Compatibility:** this is a CLOSED enum, with the same cost as
  `AuditAction`: a client generated against an older version of this
  document rejects a problem carrying a code added after it was built.
  Regenerate on upgrade, or decode the field as a plain string. It is
  enumerated anyway so that what a client must translate is knowable
  from the contract alone.
- `instance` (string, optional): Example: `"/introspect"`.
- `request_id` (string, optional): Example: `"8d94ed49-1caf-4ab3-9594-557142f2d023"`.
- `trace_id` (string, optional): Distributed-trace id (hex) for correlating this response with traces and logs. Present only when tracing is enabled and the request was recorded. Opaque and safe to log/share. Example: `"4bf92f3577b34da6a3ce929d0e0e4736"`.
- `errors` (array of objects, optional): Field-level violations (present for validation errors).

  Each item: A single field-level validation violation.
  - `field` (string, required): Offending field, dotted for nested bodies (e.g. child.id). Example: `"token_type_hint"`.
  - `code` (string, required): Machine-readable violation code: required, too_short, too_long, pattern, invalid_format, not_allowed, minimum, maximum, multiple_of, too_few, too_many, not_unique, malformed. Example: `"not_allowed"`.
  - `message` (string, required): Example: `"field token_type_hint must be one of [access_token, refresh_token]"`.

### 429 Too Many Requests

Too many requests.

Content type: `application/problem+json`

RFC 9457 (formerly RFC 7807) problem details (application/problem+json). `code` is a stable machine-readable slug for the problem kind (it mirrors the last path segment of `type`), so consumers can branch on it without parsing prose. `type` is `https://nauthera.io/problems/<code>`, which resolves to the code's runbook: why it happened, who is affected and what to do (RFC 9457 §3.1.1). A problem without a code has `about:blank`. `errors` is present on validation failures and lists every offending field.

- `title` (string, required): Example: `"Request validation failed"`.
- `status` (integer, required): Example: `400`.
- `type` (string (uri), optional): Example: `"https://nauthera.io/problems/validation_error"`.
- `detail` (string, optional): Example: `"One or more fields did not satisfy the API schema."`.
- `code` (enum, optional): The kind of problem, as a stable machine-readable slug a client branches on and translates rather than parsing `title` or `detail`, which are English prose for logs. A problem whose status says everything the server knows carries no `code`. One of: `"invalid_request"`, `"invalid_credentials"`, `"forbidden"`, `"unauthorized"`, `"insufficient_user_authentication"`, `"protected_account"`, `"password_held_by_directory"`, `"last_role_administrator"`, `"invalid_code"`, `"too_many_requests"`, `"too_many_attempts"`, `"account_locked"`, `"account_unavailable"`, `"already_enrolled"`, `"registration_disabled"`, `"registration_conflict"`, `"captcha_failed"`, `"invalid_reset_link"`, `"password_reused"`, `"password_too_short"`, `"password_too_long"`, `"password_compromised"`, `"invalid_verification_link"`, `"invalid_invitation_link"`, `"mail_relay_missing"`, `"email_unverified"`, `"not_found"`, `"conflict"`, `"unavailable"`, `"busy"`, `"window_too_large"`, `"too_many_series"`, `"internal_error"`, `"rate_limited"`, `"malformed"`, `"body_too_large"`, `"required"`, `"validation_error"`, `"unknown_org"`, `"invalid_token"`, `"invalid_dpop_proof"`, `"use_dpop_nonce"`, `"server_error"`.

  `validation_error` is the one code whose message is not a sentence about
  the code itself: the request failed the schema, and the specifics are
  the `errors` list — one entry per offending field — which a form renders
  on those fields. `required` and `malformed` are spelled like two of the
  per-field violation codes (`FieldError.code`) but mean something else;
  the two vocabularies are told apart by position, never by spelling.

  **Compatibility:** this is a CLOSED enum, with the same cost as
  `AuditAction`: a client generated against an older version of this
  document rejects a problem carrying a code added after it was built.
  Regenerate on upgrade, or decode the field as a plain string. It is
  enumerated anyway so that what a client must translate is knowable
  from the contract alone.
- `instance` (string, optional): Example: `"/introspect"`.
- `request_id` (string, optional): Example: `"8d94ed49-1caf-4ab3-9594-557142f2d023"`.
- `trace_id` (string, optional): Distributed-trace id (hex) for correlating this response with traces and logs. Present only when tracing is enabled and the request was recorded. Opaque and safe to log/share. Example: `"4bf92f3577b34da6a3ce929d0e0e4736"`.
- `errors` (array of objects, optional): Field-level violations (present for validation errors).

  Each item: A single field-level validation violation.
  - `field` (string, required): Offending field, dotted for nested bodies (e.g. child.id). Example: `"token_type_hint"`.
  - `code` (string, required): Machine-readable violation code: required, too_short, too_long, pattern, invalid_format, not_allowed, minimum, maximum, multiple_of, too_few, too_many, not_unique, malformed. Example: `"not_allowed"`.
  - `message` (string, required): Example: `"field token_type_hint must be one of [access_token, refresh_token]"`.

### Error

Problem details (RFC 9457) with an RFC 6750 / RFC 9449 challenge.

Content type: `application/problem+json`

Headers:
- `Cache-Control` = `no-store`
- `WWW-Authenticate` (string)
- `DPoP-Nonce` (string)

RFC 9457 (formerly RFC 7807) problem details (application/problem+json). `code` is a stable machine-readable slug for the problem kind (it mirrors the last path segment of `type`), so consumers can branch on it without parsing prose. `type` is `https://nauthera.io/problems/<code>`, which resolves to the code's runbook: why it happened, who is affected and what to do (RFC 9457 §3.1.1). A problem without a code has `about:blank`. `errors` is present on validation failures and lists every offending field.

- `title` (string, required): Example: `"Request validation failed"`.
- `status` (integer, required): Example: `400`.
- `type` (string (uri), optional): Example: `"https://nauthera.io/problems/validation_error"`.
- `detail` (string, optional): Example: `"One or more fields did not satisfy the API schema."`.
- `code` (enum, optional): The kind of problem, as a stable machine-readable slug a client branches on and translates rather than parsing `title` or `detail`, which are English prose for logs. A problem whose status says everything the server knows carries no `code`. One of: `"invalid_request"`, `"invalid_credentials"`, `"forbidden"`, `"unauthorized"`, `"insufficient_user_authentication"`, `"protected_account"`, `"password_held_by_directory"`, `"last_role_administrator"`, `"invalid_code"`, `"too_many_requests"`, `"too_many_attempts"`, `"account_locked"`, `"account_unavailable"`, `"already_enrolled"`, `"registration_disabled"`, `"registration_conflict"`, `"captcha_failed"`, `"invalid_reset_link"`, `"password_reused"`, `"password_too_short"`, `"password_too_long"`, `"password_compromised"`, `"invalid_verification_link"`, `"invalid_invitation_link"`, `"mail_relay_missing"`, `"email_unverified"`, `"not_found"`, `"conflict"`, `"unavailable"`, `"busy"`, `"window_too_large"`, `"too_many_series"`, `"internal_error"`, `"rate_limited"`, `"malformed"`, `"body_too_large"`, `"required"`, `"validation_error"`, `"unknown_org"`, `"invalid_token"`, `"invalid_dpop_proof"`, `"use_dpop_nonce"`, `"server_error"`.

  `validation_error` is the one code whose message is not a sentence about
  the code itself: the request failed the schema, and the specifics are
  the `errors` list — one entry per offending field — which a form renders
  on those fields. `required` and `malformed` are spelled like two of the
  per-field violation codes (`FieldError.code`) but mean something else;
  the two vocabularies are told apart by position, never by spelling.

  **Compatibility:** this is a CLOSED enum, with the same cost as
  `AuditAction`: a client generated against an older version of this
  document rejects a problem carrying a code added after it was built.
  Regenerate on upgrade, or decode the field as a plain string. It is
  enumerated anyway so that what a client must translate is knowable
  from the contract alone.
- `instance` (string, optional): Example: `"/introspect"`.
- `request_id` (string, optional): Example: `"8d94ed49-1caf-4ab3-9594-557142f2d023"`.
- `trace_id` (string, optional): Distributed-trace id (hex) for correlating this response with traces and logs. Present only when tracing is enabled and the request was recorded. Opaque and safe to log/share. Example: `"4bf92f3577b34da6a3ce929d0e0e4736"`.
- `errors` (array of objects, optional): Field-level violations (present for validation errors).

  Each item: A single field-level validation violation.
  - `field` (string, required): Offending field, dotted for nested bodies (e.g. child.id). Example: `"token_type_hint"`.
  - `code` (string, required): Machine-readable violation code: required, too_short, too_long, pattern, invalid_format, not_allowed, minimum, maximum, multiple_of, too_few, too_many, not_unique, malformed. Example: `"not_allowed"`.
  - `message` (string, required): Example: `"field token_type_hint must be one of [access_token, refresh_token]"`.

## Example request

```bash
curl -g 'https://auth.example.com/v1/admin/orgs/{slug}/users' \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

## Example response (200)

```json
{
  "items": [
    {
      "id": "3f1a5b2c-8d4e-4f6a-9b0c-1d2e3f4a5b6c",
      "username": "alice@example.com",
      "email": "alice@example.com",
      "second_factors": [
        "totp"
      ],
      "password_set": false,
      "password_change_required": false,
      "invitation_pending": false,
      "failed_login_attempts": 0,
      "status": "active",
      "created_at": "2026-01-15T09:30:00Z",
      "updated_at": "2026-01-15T09:30:00Z"
    }
  ],
  "page": {
    "limit": 50,
    "sort": "last_name,-first_name,id",
    "has_more": false,
    "has_prev": false
  }
}
```
