# Get an audit event (admin)

`GET /v1/admin/orgs/{slug}/audit-events/{id}`

Returns one audit record by id, subject to the same tenancy rules as the
listing: a record belonging to another organization reads as missing, so a
caller cannot use this to probe for one.

Ids are time-ordered (UUIDv7), so the server derives the window the record
must lie in from the id itself and reads a single partition rather than
searching the whole retention period.

The read is audited with the id it asked for, found or not.

## 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.audit: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 [`audit.read`](https://www.nauthera.io/docs/v1/api#permission-audit.read): Read an organization's audit log.

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.
- `id` (string, required): The audit record id (UUIDv7). Being time-ordered, it also tells the server which partition to read. Constraints: 1–64 characters.

## 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

The audit record.

Content type: `application/json`

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

One audit record.

The server NEVER returns a rendered sentence. It returns the event's
`action` plus structured actor/target/metadata, and the client renders
the human text by looking up one translation key and interpolating. That
is what makes the log translatable, and it is why there is no `message`
field to reach for: a stored English sentence cannot be translated,
cannot be filtered on, and becomes an accidental API that changes on
every refactor.

Two fields carry translation keys: `action` (`auditEvents:<action>`) and
`reason` (`auditReasons:<reason>`). Both are enumerated vocabularies, so
this document alone is enough to build complete translation coverage —
which is the reason they are enumerated.

That completeness is bought with a compatibility cost, stated on
`AuditAction`: a client generated against one version of this document
rejects an entire audit page the first time the server emits a value
added after that client was built. Regenerate on upgrade. A client that
would rather degrade gracefully than fail closed should decode `action`
and `reason` as plain strings and keep a fallback label — the values are
stable and append-only, so an unrecognized one is always a new value and
never a renamed one.

- `id` (string, required): The record id (UUIDv7, so it is also creation-ordered).
- `occurred_at` (string (date-time), required): When the event happened.
- `recorded_at` (string (date-time), required): When the record was persisted. It differs from `occurred_at` by however long the event was buffered, so the gap is the write pipeline's lag.
- `action` (enum, required): The event type — what was attempted. Append-only: an action is never renamed once shipped, because a rename orphans every stored record and every SIEM rule matching on it. One of: `"admin.access.denied"`, `"audit.counts.read"`, `"audit.log.read"`, `"audit.ranking.read"`, `"auth.account.locked"`, `"auth.email_verification.completed"`, `"auth.email_verification.requested"`, `"auth.invitation.accepted"`, `"auth.password_reset.completed"`, `"auth.password_reset.requested"`, `"auth.rate_limit.triggered"`, `"auth.registration.completed"`, `"auth.sign_in.alerted"`, `"auth.sign_in.completed"`, `"auth.sign_out.completed"`, `"branding.applied"`, `"branding.deleted"`, `"broker_membership.accepted"`, `"broker_membership.clients_set"`, `"broker_membership.ended"`, `"broker_membership.invited"`, `"broker_membership.listed"`, `"client.adopted"`, `"client.applied"`, `"client.configuration.read"`, `"client.configured"`, `"client.deleted"`, `"client.deregistered"`, `"client.listed"`, `"client.name.suspicious"`, `"client.provisioned"`, `"client.read"`, `"client.registered"`, `"client.registration.attempted"`, `"client.secret.issued"`, `"client.usage.read"`, `"email_domain.claimed"`, `"email_domain.listed"`, `"email_domain.lost"`, `"email_domain.removed"`, `"email_domain.verified"`, `"http.cleartext_credential.received"`, `"http.forwarding.ignored"`, `"http.header.repeated"`, `"http.request.screened"`, `"http.request.served"`, `"identity_provider.applied"`, `"identity_provider.deleted"`, `"identity_provider.listed"`, `"ldap_directory.applied"`, `"ldap_directory.deleted"`, `"mfa.email.activated"`, `"mfa.email.challenge_started"`, `"mfa.email.enrollment_started"`, `"mfa.email.revoked"`, `"mfa.email.verified"`, `"mfa.factor.locked"`, `"mfa.passkey.challenge_started"`, `"mfa.passkey.enrollment_started"`, `"mfa.passkey.offer_declined"`, `"mfa.passkey.registered"`, `"mfa.passkey.verified"`, `"mfa.recovery_code.consumed"`, `"mfa.recovery_codes.acknowledged"`, `"mfa.recovery_codes.regenerated"`, `"mfa.request.authenticated"`, `"mfa.status.read"`, `"mfa.totp.activated"`, `"mfa.totp.disabled"`, `"mfa.totp.enrollment_started"`, `"mfa.totp.verified"`, `"oauth.authorization.requested"`, `"oauth.client_authentication.attempted"`, `"oauth.consent.decided"`, `"oauth.consent.revoked"`, `"oauth.device.decided"`, `"oauth.device.redeemed"`, `"oauth.device.requested"`, `"oauth.device.user_code.attempted"`, `"oauth.token.introspected"`, `"oauth.token.issued"`, `"oauth.token.presented"`, `"oauth.token.refreshed"`, `"oauth.token.revoked"`, `"oidc.logout.frontchannel.rendered"`, `"oidc.logout.initiated"`, `"oidc.logout.participant.added"`, `"oidc.logout.participant.skipped"`, `"oidc.logout.token.delivered"`, `"oidc.userinfo.read"`, `"organization.created"`, `"organization.deleted"`, `"organization.listed"`, `"organization.mail.tested"`, `"organization.read"`, `"organization.route.attempted"`, `"organization.snapshot.read"`, `"organization.updated"`, `"resource_problem.listed"`, `"resource_problem.read"`, `"resource_status.reported"`, `"role.granted"`, `"role.revoked"`, `"session.created"`, `"session.history.listed"`, `"session.listed"`, `"session.presented"`, `"session.revoked"`, `"system.admin.bootstrapped"`, `"system.audit.checkpoint"`, `"system.audit.configured"`, `"system.audit.degraded"`, `"system.audit.overflow"`, `"system.audit.partition_created"`, `"system.audit.partition_dropped"`, `"system.audit.recovered"`, `"system.audit.write_delayed"`, `"system.audit.write_failed"`, `"system.debug.accessed"`, `"system.dependency.degraded"`, `"system.dependency.recovered"`, `"system.keys.loaded"`, `"system.migration.applied"`, `"system.session.reaped"`, `"system.signing_key.created"`, `"system.signing_key.dropped"`, `"system.started"`, `"system.stopped"`, `"system.transport.configured"`, `"threats.overview.read"`, `"user.blocked"`, `"user.created"`, `"user.deleted"`, `"user.devices.listed"`, `"user.identities.listed"`, `"user.invitation.sent"`, `"user.listed"`, `"user.password.changed"`, `"user.password_change.required"`, `"user.password_reset.sent"`, `"user.reactivated"`, `"user.read"`, `"user.remembered_browsers.forgotten"`, `"user.suspended"`, `"user.unlocked"`, `"user.updated"`.

  The outcome is deliberately NOT part of the name. `auth.sign_in.completed`
  with `outcome: failure` is a failed sign-in; there is no
  `auth.sign_in.failed`. Encoding it would double this list, the catalog and
  every translation file.

  **Translation key:** the action verbatim in the `auditEvents` namespace —
  `auditEvents:user.password.changed`. Because i18next splits keys on `.`,
  a locale file nests naturally by segment. The catalog endpoint publishes
  the same key as `i18n_key` so a client need not re-derive it.

  **Compatibility:** this is a CLOSED enum, and that has a cost worth
  stating plainly. A client generated against one version of this document
  will reject an entire audit page — not merely one record — the first time
  the server emits an action added after that client was built. Regenerate
  on upgrade, or decode the field as a plain string. It is enumerated
  anyway because the alternative left every consumer unable to know the
  translatable vocabulary without calling the catalog endpoint at runtime.
- `category` (enum, required): The group an action belongs to, for faceting. Declared per action rather than derived from the action's first segment, because the two do not always agree: `oauth.*` and `oidc.*` events are both "token" work to an operator, and the grouping a UI offers should follow what an operator is looking for rather than how the constant is spelled. One of: `"authentication"`, `"session"`, `"mfa"`, `"token"`, `"user"`, `"organization"`, `"client"`, `"role"`, `"admin"`, `"audit"`, `"system"`.
- `outcome` (enum, required): How an attempt ended. `denied` is separated from `failure` because "you were not allowed" and "you got it wrong" are different security signals — a cluster of the former is what a SIEM alerts on. `error` marks a failure attributable to the server, so an operator can tell "users are getting it wrong" from "we are broken". The server enforces the pairing: `error` always carries the reason `internal_error` or `unavailable`, and those two reasons never appear under any other outcome. One of: `"success"`, `"failure"`, `"denied"`, `"error"`.
- `severity` (enum, required): How much an event matters, lowest first. Assigned per action, and an occurrence may raise it but never lower it. A refusal or a server fault is raised to at least `warning` whatever its action's default, so `severity >= warning` selects everything that did not go as asked; a protocol step that only looks like a failure (`authorization_pending`, `use_dpop_nonce`, `mfa_required`, `passkey_offered`, `login_required`, `interaction_required`, `consent_required`, `account_selection_required`) keeps its action's default. One of: `"info"`, `"notice"`, `"warning"`, `"critical"`.
- `organization_id` (string, required): The organization the event is ABOUT. Empty for a platform-scoped record (process startup, retention) — those have no owning tenant and are visible only through the main organization.
- `cross_tenant` (boolean, required): Whether the actor acted from a different organization than the one the event is about — a main-org administrator operating on a tenant. Derived from the two organization ids.
- `actor` (object, required): Who caused the event.

  `display` is a snapshot taken when the event was recorded, not a live
  lookup. That is deliberate: an audit record's most important reads happen
  after the fact, by which time the principal it names may have been
  deleted — and a log that can only render an opaque id once the row is
  gone has failed at its only job.
  - `type` (enum, required): The kind of principal that caused an event. `anonymous` is the correct value for every pre-authentication event: a failed sign-in has no principal yet, and the address that was typed is in the event metadata instead — putting it in the actor id would mint a phantom identifier that joins against nothing. One of: `"user"`, `"admin"`, `"client"`, `"system"`, `"anonymous"`.
  - `id` (string, optional)
  - `display` (string, optional): A human label captured at event time.
  - `impersonator_id` (string, optional): The real principal, when this actor was being acted-as. Present only for actions taken inside an impersonated session.
  - `impersonator_display` (string, optional)
- `reason` (enum, optional): A stable machine token explaining a non-success outcome. Absent on success. One of: `"invalid_credentials"`, `"account_locked"`, `"rate_limited"`, `"mfa_required"`, `"email_verification_required"`, `"invalid_code"`, `"factor_locked"`, `"missing_role"`, `"missing_scope"`, `"cross_org_denied"`, `"not_found"`, `"conflict"`, `"invalid_request"`, `"invalid_client"`, `"invalid_grant"`, `"invalid_redirect_uri"`, `"unauthenticated"`, `"registration_closed"`, `"invalid_target"`, `"invalid_dpop_proof"`, `"repeated_header"`, `"token_expired"`, `"missing_expiry"`, `"audience_mismatch"`, `"unsupported_critical"`, `"unregistered_client"`, `"redirect_uri_required"`, `"pushed_request_required"`, `"invalid_request_uri"`, `"request_uri_client_mismatch"`, `"invalid_request_object"`, `"unavailable"`, `"internal_error"`, `"access_denied"`, `"login_required"`, `"consent_required"`, `"interaction_required"`, `"unsupported_grant_type"`, `"invalid_scope"`, `"unauthorized_client"`, `"unsupported_response_type"`, `"account_selection_required"`, `"invalid_authorization_details"`, `"expired_token"`, `"request_not_supported"`, `"request_uri_not_supported"`, `"unsupported_token_type"`, `"invalid_token"`, `"use_dpop_nonce"`, `"authorization_pending"`, `"slow_down"`, `"invalid_client_metadata"`, `"invalid_software_statement"`, `"unapproved_software_statement"`, `"account_unavailable"`, `"insufficient_user_authentication"`, `"protected_account"`, `"passkey_offered"`, `"password_change_required"`.

  Translate it rather than displaying it: **the key is the token verbatim
  in the `auditReasons` namespace** — `auditReasons:invalid_credentials`. A
  separate namespace from actions, because these are flat tokens rather
  than dotted paths and would otherwise nest unpredictably.

  The OAuth-related values are spelled exactly as the corresponding RFC
  6749 §5.2 / OIDC Core §3.1.2.6 wire error, so the audit record and the
  error the client actually received are the same string.

  **Compatibility:** closed, with the same cost described on `AuditAction` —
  this vocabulary grows as the server implements more specifications
  (`invalid_target` arrived with RFC 8707, `invalid_dpop_proof` with RFC
  9449).
- `actor_organization_id` (string, optional): The organization the ACTOR came from.
- `target` (object, optional): The object the event acted upon. Absent for events that act on nothing in particular. `display` is a snapshot, for the same reason as on the actor.
  - `type` (enum, optional): The kind of object an event acted upon. `audit_log` is the log itself, for the events that record reading or exporting it. `consent` is one user's standing agreement with one client for one resource, named by the agreement's id, so every decision about it shares one target. One of: `"user"`, `"organization"`, `"client"`, `"session"`, `"token"`, `"factor"`, `"role"`, `"system"`, `"audit_log"`, `"consent"`.
  - `id` (string, optional)
  - `display` (string, optional)
- `client_ip` (string, optional): The caller's address, resolved through the trusted-proxy policy.
- `client_ip_country` (string, optional): The ISO 3166-1 alpha-2 code of the country `client_ip` is in, read when the record is returned. Present only when the server has an IP geolocation database (`geoip.database`) that knows the address. Constraints: pattern `^[A-Z]{2}$`. Example: `"CH"`.
- `client_ip_source` (enum, optional): How a client address was determined. `socket` means it is the connecting peer's own address, from a peer that forwarded no client, so it came off the connection and the caller could not have chosen it; `forwarded` means it came from a header set by a peer inside the trusted-proxy list; `proxy` means the request came through a trusted proxy that forwarded but named no client to believe, so the address is the proxy's own. One of: `"socket"`, `"forwarded"`, `"proxy"`.
- `transport` (enum, optional): Whether the client's own hop to the deployment was encrypted, and on whose word. `tls`: this listener completed a TLS handshake and no trusted proxy reported a scheme; `cleartext`: it did not, and no trusted proxy reported a scheme. `forwarded_tls` and `forwarded_cleartext` are a trusted proxy's report (X-Forwarded-Proto): every hop it recorded was https, or one was not. It is the same verdict that decides whether the response carries Strict-Transport-Security, and it is computed by the server, never copied from a header. Absent on records with no request behind them (startup, maintenance, the pipeline's own records) and on the summary of a coalesced refusal, whose requests may have arrived differently; the summary of a coalesced edge finding (`http.forwarding.ignored`, `http.cleartext_credential.received`) carries the transport its window is keyed on. One of: `"tls"`, `"cleartext"`, `"forwarded_tls"`, `"forwarded_cleartext"`.
- `proxy_ip` (string, optional): The trusted proxy that vouched for a forwarded `client_ip`: the address of the peer that connected to this server, present when `client_ip_source` is `forwarded` (every other source already puts that peer in `client_ip`). It is also set on a coalesced summary of forwarded requests (`http.cleartext_credential.received` carrying a `repeat_count`, whose window is the proxy's rather than a client's), where `client_ip` and `client_ip_source` are absent because the clients varied. A forwarded address is only as true as the peer that wrote it, and this names the peer.
- `proxy_ip_country` (string, optional): The ISO 3166-1 alpha-2 code of the country `proxy_ip` is in, as `client_ip_country` is read. Constraints: pattern `^[A-Z]{2}$`. Example: `"CH"`.
- `user_agent` (string, optional): The raw User-Agent, stored unparsed.
- `client_kind` (enum, optional): The kind of client the record's User-Agent claims sent the request: `browser`, a browser a person drives; `ai_agent`, an AI crawler, assistant or agent (a training crawler, a page fetched for an assistant's user, a coding agent signing in to a tool); `crawler`, a search, link-preview or SEO crawler; `automation`, a headless or test-driven browser; `library`, a program calling directly (an HTTP library, a command-line tool, a native app, a health probe); `unknown`, a header that is empty or names none of these. It is read from the stored `user_agent` each time the record is served, so every record carries the server's current reading. A User-Agent is whatever its sender writes, so this is a claim, never a verified identity. Absent on records no request produced (startup, maintenance, the pipeline's own records) and on the summary of a coalesced refusal. One of: `"browser"`, `"ai_agent"`, `"crawler"`, `"automation"`, `"library"`, `"unknown"`.
- `client_agent` (string, optional): The product a client other than a browser names in its User-Agent, as its vendor names it (`ClaudeBot`, `Claude Code`, `curl`), from the list the server recognizes. Absent when the header names none of them; never text copied from the header.
- `request_id` (string, optional): Matches the `X-Request-ID` echoed to the caller. An inbound `X-Request-ID` is kept only from a trusted proxy; otherwise the server mints the id.
- `trace_id` (string, optional): W3C trace id, for correlating with distributed traces. An inbound `traceparent` is honored only from a trusted proxy.
- `span_id` (string, optional)
- `operation` (string, optional): The OpenAPI operationId that served the request, exactly as this document spells it (`getJWKS`, `signIn`). A route mounted outside the document carries the name it declares: the per-organization discovery and key-set routes the operationId of the document they serve, the others a stable name of their own (`authUI`, `dashboard`, `healthz`, `pprof`). A control-plane event carries the gRPC full method name (`/package.Service/Method`). Empty when the request was refused before it was routed. Records stored by an earlier release carry the generated Go identifier instead (`GetJWKS`), so a query spanning the change matches both spellings. Stored instead of a request path, which would be unbounded in cardinality and would embed user ids and email addresses.
- `http_method` (string, optional): The request method. A method outside the registered set (RFC 9110 §9 and PATCH) is recorded as `_OTHER`, as OpenTelemetry records it.
- `session_id` (string, optional): The public id of the login session the event belongs to, the `sid` relying parties receive. An event an administrator caused through the admin API carries the session their access token was issued in.
- `client_id` (string, optional): The OAuth client the server resolved the event to: the client that authenticated, the registered client a client authentication named (whether or not it succeeded), or the client of a verified token, session or flow. It says which registered client the event concerns, not that the request proved it was that client. An event an administrator caused names the admin client their access token was minted for (the console or the machine client), never a client they manage. Any other administrative or client lifecycle event never sets it; it names the client as its target.
- `claimed_client_id` (string, optional): An OAuth client the event names that the server did not resolve it to: the `client_id` a sign-in form carried, an identifier a client authentication presented that no client registered, a logout's unverified hint. Present only when `client_id` is absent, so a record names its client in at most one of the two, and which one says whether the server resolved the client or the caller only named it. A named value is whatever the caller sent, bounded. Records written before the server kept this field carry it in `metadata.client_id` only.
- `metadata` (object, optional): Per-action detail. Its keys are declared per action in the server's catalog (see the catalog endpoint) and anything undeclared is dropped before storage, so it can never carry credentials. A field with a closed vocabulary (the catalog's `field_values`) only ever holds a value from it. Two reserved keys may appear: `_dropped` lists keys that were removed — undeclared, or carrying a value outside their vocabulary — and `_truncated` lists keys whose values were shortened to fit the size bounds, so each such stored value is a prefix of the one the server was given. A key is never removed for its size.

### 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]"`.

### 404 Not Found

Problem details (RFC 9457).

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]"`.

### 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}/audit-events/{id}' \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

## Example response (200)

```json
{
  "id": "string",
  "occurred_at": "2026-01-15T09:30:00Z",
  "recorded_at": "2026-01-15T09:30:00Z",
  "action": "admin.access.denied",
  "category": "authentication",
  "outcome": "success",
  "severity": "info",
  "organization_id": "string",
  "cross_tenant": false,
  "actor": {
    "type": "user"
  },
  "client_ip_country": "CH",
  "proxy_ip_country": "CH"
}
```
