{"openapi":"3.2.0","info":{"title":"Nauthera Public API","version":"1.0.0","summary":"OAuth2 and OpenID Connect endpoints served on the public listener.","description":"The public-facing identity API of nauthera-server. Implements the OAuth 2.1\n+ OpenID Connect provider contract: discovery (OIDC Discovery 1.0 and RFC\n8414 AS metadata), the JSON Web Key Set, the authorization and token\nendpoints (with PKCE, device, token-exchange and refresh grants), pushed\nauthorization requests, token introspection and revocation, UserInfo,\ndynamic client registration + management, and RP-initiated logout.\n\nSender-constraining (DPoP, RFC 9449) and issuer identification (RFC 9207)\nare supported per the OAuth 2.0 Security BCP (RFC 9700). Flow logic is\nscaffolded in this bootstrap: not-yet-implemented flows return spec-shaped\n`temporarily_unavailable` errors.\n","termsOfService":"https://nauthera.io/terms","contact":{"name":"Nauthera Maintainers","url":"https://github.com/nauthera/nauthera-server","email":"security@nauthera.io"},"license":{"name":"Apache-2.0","url":"https://www.apache.org/licenses/LICENSE-2.0"}},"externalDocs":{"description":"Architecture and configuration docs","url":"https://github.com/nauthera/nauthera-server/tree/main/docs"},"servers":[{"url":"https://auth.example.com","description":"Production issuer (replace with your issuer.url)"},{"url":"http://localhost:8080","description":"Local development (just run)"}],"tags":[{"name":"Discovery","description":"OpenID Connect discovery, AS metadata, and key material."},{"name":"OAuth2","description":"OAuth2 protocol endpoints (RFC 6749, 7009, 7662, 8628, 8693, 9126)."},{"name":"OpenID Connect","description":"OpenID Connect protocol endpoints (OIDC Core, RP-Initiated Logout)."},{"name":"Client Registration","description":"Dynamic client registration and management (RFC 7591, 7592)."},{"name":"Interactive","description":"Browser-facing endpoints backing the hosted auth UI (/ui): password sign-in, local registration, and sign-out. Same-origin JSON; not part of the OAuth2/OIDC wire protocol."},{"name":"Admin","description":"Privileged administrative endpoints for managing organizations (orgs) and\ntheir local users (CRUD), versioned under `/v1/admin`. Authenticated by a\nBearer access token: every operation enforces its declared `nauthera.*`\nscope (via the `oauth2` security scheme) IN ADDITION to the permission it\ndeclares in `x-permission`, which the caller's roles must grant. Organization\nendpoints are main-org only; user and audit endpoints are confined to the\ncaller's own org unless a platform role held in the main org admits them to\nevery org. All list endpoints use opaque\ncursor (keyset) pagination — bound the page with `limit`, order it with\n`sort` (per-endpoint `x-sort-fields`), then pass the previous response's\n`page.next_cursor` back as `cursor` for as long as `page.has_more` is true.\nNot part of the OAuth2/OIDC wire protocol.\n"}],"security":[{"bearerAuth":[]}],"paths":{"/.well-known/openid-configuration":{"get":{"operationId":"getOpenIDConfiguration","tags":["Discovery"],"summary":"OpenID Connect discovery document","description":"Returns the issuer's OpenID Provider Metadata (OIDC Discovery 1.0), a\nsuperset of the RFC 8414 authorization-server metadata. Cacheable\n(`Cache-Control: public, max-age=300`).\n","security":[],"x-cache":{"ttl_seconds":300},"responses":{"200":{"description":"Discovery metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizationServerMetadata"},"examples":{"default":{"summary":"A typical discovery document","value":{"issuer":"https://auth.example.com","authorization_endpoint":"https://auth.example.com/authorize","token_endpoint":"https://auth.example.com/token","userinfo_endpoint":"https://auth.example.com/userinfo","jwks_uri":"https://auth.example.com/.well-known/jwks.json","registration_endpoint":"https://auth.example.com/register","introspection_endpoint":"https://auth.example.com/introspect","revocation_endpoint":"https://auth.example.com/revoke","device_authorization_endpoint":"https://auth.example.com/device_authorization","pushed_authorization_request_endpoint":"https://auth.example.com/par","end_session_endpoint":"https://auth.example.com/end_session","response_types_supported":["code"],"response_modes_supported":["query","fragment","form_post"],"subject_types_supported":["public"],"id_token_signing_alg_values_supported":["ES256"],"scopes_supported":["openid","profile","email","offline_access"],"token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","none","private_key_jwt"],"grant_types_supported":["authorization_code","refresh_token","client_credentials","urn:ietf:params:oauth:grant-type:device_code","urn:ietf:params:oauth:grant-type:token-exchange"],"code_challenge_methods_supported":["S256"],"dpop_signing_alg_values_supported":["ES256"],"authorization_response_iss_parameter_supported":true,"require_pushed_authorization_requests":false}}}}}}}}},"/.well-known/oauth-authorization-server":{"get":{"operationId":"getOAuthServerMetadata","tags":["Discovery"],"summary":"OAuth 2.0 Authorization Server Metadata (RFC 8414)","description":"Returns the OAuth 2.0 authorization-server metadata (RFC 8414). Shares\nthe metadata schema with the OpenID discovery document; OIDC-only fields\nmay be present. Cacheable (`Cache-Control: public, max-age=300`).\n","security":[],"x-cache":{"ttl_seconds":300},"responses":{"200":{"description":"Authorization server metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizationServerMetadata"}}}}}}},"/.well-known/jwks.json":{"get":{"operationId":"getJWKS","tags":["Discovery"],"summary":"JSON Web Key Set (public signing keys)","description":"The public keys clients use to verify issued tokens (RFC 7517).","security":[],"x-cache":{"ttl_seconds":300},"responses":{"200":{"description":"The active public verification keys.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JWKS"},"examples":{"default":{"value":{"keys":[{"kty":"EC","use":"sig","kid":"7Xy1t...abc","alg":"ES256","crv":"P-256","x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU","y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"}]}}}}}}}}},"/authorize":{"get":{"operationId":"authorize","tags":["OAuth2"],"summary":"OAuth2 / OIDC authorization endpoint","description":"Starts the authorization-code flow (RFC 6749 §4.1, OIDC Core). PKCE is\nrequired for public clients and enforced for all per RFC 9700; the\n`code_challenge` is bound to the code and verified at the token endpoint.\n\nOn success redirects (302) to the client's exactly-registered\n`redirect_uri` with `code`, `state`, and `iss` (RFC 9207). On error the\nserver returns a direct error and never redirects to an unvalidated URI.\n\nSupports pushed authorization requests via `request_uri` (RFC 9126),\nrequest objects via `request` / `request_uri` (RFC 9101 JAR), rich\nauthorization via `authorization_details` (RFC 9396), resource\nindicators (RFC 8707), and DPoP key binding via `dpop_jkt` (RFC 9449).\n","security":[],"x-rate-limit":true,"parameters":[{"name":"response_type","in":"query","required":false,"description":"The grant/response type. `code` for the authorization-code flow. Required unless the request arrives by `request_uri` (RFC 9126), in which case it comes from the pushed request.","schema":{"type":"string","enum":["code"],"example":"code"}},{"name":"client_id","in":"query","required":true,"description":"The registered client identifier (printable ASCII, no spaces).","schema":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[!-~]+$","example":"nauthera_2Rk9"}},{"name":"redirect_uri","in":"query","required":false,"description":"Must exactly match a registered redirect URI (RFC 9700 §2.1).","schema":{"type":"string","format":"uri","maxLength":2048,"example":"https://app.example.com/callback"}},{"name":"scope","in":"query","required":false,"description":"Space-delimited scopes. `openid` requests an ID token.","schema":{"type":"string","maxLength":2048,"pattern":"^\\S+( \\S+)*$","example":"openid profile email"}},{"name":"state","in":"query","required":false,"description":"Opaque CSRF value echoed back on the response.","schema":{"type":"string","maxLength":1024,"example":"af0ifjsldkj"}},{"name":"nonce","in":"query","required":false,"description":"OIDC value bound into the ID token to mitigate replay.","schema":{"type":"string","maxLength":1024,"example":"n-0S6_WzA2Mj"}},{"name":"response_mode","in":"query","required":false,"description":"How the authorization response is returned to the client.","schema":{"type":"string","enum":["query","fragment","form_post"],"example":"query"}},{"name":"prompt","in":"query","required":false,"description":"Space-delimited OIDC prompt values (none, login, consent, select_account).","schema":{"type":"string","maxLength":64,"pattern":"^(none|login|consent|select_account)( (none|login|consent|select_account))*$","example":"consent"}},{"name":"display","in":"query","required":false,"schema":{"type":"string","enum":["page","popup","touch","wap"]}},{"name":"max_age","in":"query","required":false,"description":"Maximum authentication age in seconds (OIDC).","schema":{"type":"integer","minimum":0,"maximum":315360000,"example":3600}},{"name":"login_hint","in":"query","required":false,"schema":{"type":"string","maxLength":512}},{"name":"id_token_hint","in":"query","required":false,"description":"A previously issued ID token hinting the current session.","schema":{"type":"string","maxLength":8192}},{"name":"ui_locales","in":"query","required":false,"description":"Space-delimited BCP47 language tags for the UI.","schema":{"type":"string","maxLength":256,"pattern":"^\\S+( \\S+)*$","example":"en-US fr-CA"}},{"name":"acr_values","in":"query","required":false,"description":"Space-delimited requested Authentication Context Class References.","schema":{"type":"string","maxLength":512,"pattern":"^\\S+( \\S+)*$"}},{"name":"claims","in":"query","required":false,"description":"OIDC claims request (a JSON object, URL-encoded).","schema":{"type":"string","maxLength":8192}},{"name":"code_challenge","in":"query","required":false,"description":"PKCE challenge (RFC 7636). base64url, required for public clients.","schema":{"type":"string","minLength":43,"maxLength":128,"pattern":"^[A-Za-z0-9_-]+$","example":"E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"}},{"name":"code_challenge_method","in":"query","required":false,"schema":{"type":"string","enum":["S256"],"example":"S256"}},{"name":"request","in":"query","required":false,"description":"A JWT-Secured Authorization Request object (RFC 9101 JAR).","schema":{"type":"string","maxLength":16384}},{"name":"request_uri","in":"query","required":false,"description":"Reference to a pushed (RFC 9126) or hosted (RFC 9101) request object, e.g. urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14.","schema":{"type":"string","maxLength":2048,"pattern":"^(urn:ietf:params:oauth:request_uri:|https://)\\S+$"}},{"name":"authorization_details","in":"query","required":false,"description":"Rich Authorization Requests (RFC 9396), a JSON array (URL-encoded).","schema":{"type":"string","maxLength":8192}},{"name":"resource","in":"query","required":false,"description":"Resource indicator (RFC 8707) identifying the target resource server.","schema":{"type":"string","format":"uri","maxLength":2048}},{"name":"dpop_jkt","in":"query","required":false,"description":"JWK SHA-256 thumbprint (base64url, 43 chars) binding the code to a DPoP key (RFC 9449).","schema":{"type":"string","minLength":43,"maxLength":43,"pattern":"^[A-Za-z0-9_-]+$"}},{"$ref":"#/components/parameters/CookieHeader"}],"responses":{"302":{"description":"Redirect back to the client with an authorization code, OR to the\nhosted login UI (/login) when no valid session exists. The\nLocation carries `code`, `state` (if supplied) and `iss` (RFC 9207)\non the success path, or the preserved authorization parameters on the\nlogin path.\n\nWhen the deployment serves OIDC Session Management 1.0 the success\nLocation additionally carries `session_state` (§3), the value the\nrelying party's iframe polls `check_session_iframe` with. It is absent\notherwise, and absent for a client whose redirect URI has no web\norigin (a native custom scheme), which has nothing to compare against.\n","headers":{"Location":{"$ref":"#/components/headers/Location"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Pragma":{"$ref":"#/components/headers/PragmaNoCache"},"Set-Cookie":{"$ref":"#/components/headers/SetCookie"}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/OAuthErrorResponse"}}},"post":{"operationId":"authorizePost","tags":["OAuth2"],"summary":"OAuth2 / OIDC authorization endpoint (POST form)","description":"The POST form of the authorization endpoint. OIDC Core §3.1.2.1: \"The\nAuthorization Server MUST support the use of the HTTP GET and POST methods\ndefined in RFC 7231 at the Authorization Endpoint.\" RFC 6749 §3.1 says the\nsame.\n\nIt behaves IDENTICALLY to GET — the same validation, the same errors, the\nsame redirect — because the method is a transport choice, not a different\nauthorization. Parameters arrive `application/x-www-form-urlencoded`\ninstead of in the query string, which keeps a long or sensitive request\n(a JAR request object, rich authorization details) out of the URL, browser\nhistory, Referer headers and proxy logs.\n\nNO cross-site check applies here, unlike the interactive endpoints. An\nauthorization request is cross-origin BY DESIGN: it is how a relying party\nhands the user to this server, and it is typically a form auto-submitted\nfrom the client's own origin. Rejecting it on Origin or Sec-Fetch-Site\nwould break the flow the endpoint exists for. Its safety comes from the\nsame places the GET form's does — the exactly-matched redirect_uri, PKCE,\nand the fact that no state changes until the user acts.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/CookieHeader"}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/PushedAuthorizationRequest"}}}},"responses":{"302":{"description":"Redirect to the client redirect_uri (or to the hosted login/consent UI).\nCarries the same `session_state` and `Set-Cookie` as the GET form —\nthe two share one handler, and a security-relevant header on only one\nof them would be a difference nothing in the specification asks for.\n","headers":{"Location":{"$ref":"#/components/headers/Location"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Set-Cookie":{"$ref":"#/components/headers/SetCookie"}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/OAuthErrorResponse"}}}},"/par":{"post":{"operationId":"pushedAuthorizationRequest","tags":["OAuth2"],"summary":"Pushed Authorization Request (RFC 9126)","description":"Accepts the authorization-request parameters directly from a\nclient-authenticated back channel and returns a single-use `request_uri`\nthe client then passes to `/authorize`. Hardens the flow against\nrequest tampering and oversized front-channel requests. `request_uri`\nMUST NOT be present in the PAR body.\n","security":[{"clientSecretBasic":[]},{}],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/PushedAuthorizationRequest"}}}},"responses":{"201":{"description":"The pushed request was accepted.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushedAuthorizationResponse"},"examples":{"default":{"value":{"request_uri":"urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY22c","expires_in":90}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/OAuthErrorResponse"}}}},"/token":{"post":{"operationId":"token","tags":["OAuth2"],"summary":"OAuth2 token endpoint","description":"Exchanges a grant for tokens (RFC 6749 §4). Supported grant types:\n`authorization_code` (with PKCE), `refresh_token` (rotated for public\nclients per RFC 9700), `client_credentials`,\n`urn:ietf:params:oauth:grant-type:device_code` (RFC 8628),\n`urn:ietf:params:oauth:grant-type:token-exchange` (RFC 8693), and\n`urn:ietf:params:oauth:grant-type:jwt-bearer` (RFC 7523).\n\nClient authentication: `client_secret_basic` (Authorization header),\n`client_secret_post` or `private_key_jwt` (`client_assertion`) in the\nbody, or none (public + PKCE). DPoP-bound tokens (RFC 9449) are issued\nwhen a valid `DPoP` proof is presented. Responses set\n`Cache-Control: no-store`.\n","security":[{"clientSecretBasic":[]},{}],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"requestBody":{"required":true,"description":"URL-encoded token request (RFC 6749 §4).","content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/TokenRequest"},"examples":{"authorizationCode":{"value":{"grant_type":"authorization_code","code":"SplxlOBeZQQYbYS6WxSbIA","redirect_uri":"https://app.example.com/callback","code_verifier":"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"}},"deviceCode":{"value":{"grant_type":"urn:ietf:params:oauth:grant-type:device_code","device_code":"GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS","client_id":"nauthera_2Rk9"}},"tokenExchange":{"value":{"grant_type":"urn:ietf:params:oauth:grant-type:token-exchange","subject_token":"eyJhbGciOi...","subject_token_type":"urn:ietf:params:oauth:token-type:access_token","requested_token_type":"urn:ietf:params:oauth:token-type:access_token","audience":"https://api.example.com"}}}}}},"responses":{"200":{"description":"A successful token response.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"DPoP-Nonce":{"$ref":"#/components/headers/DpopNonce"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"},"examples":{"bearer":{"value":{"access_token":"2YotnFZFEjr1zCsicMWpAA","token_type":"Bearer","expires_in":3600,"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA","scope":"openid profile"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/OAuthErrorResponse"}}}},"/device_authorization":{"post":{"operationId":"deviceAuthorization","tags":["OAuth2"],"summary":"Device Authorization Request (RFC 8628)","description":"Begins the device authorization grant for browserless / input-constrained\nclients. Returns a `device_code`, a user-facing `user_code`, and a\n`verification_uri`; the client then polls `/token` with\n`grant_type=urn:ietf:params:oauth:grant-type:device_code`.\n","security":[{"clientSecretBasic":[]},{}],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/AuthorizationHeader"}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/DeviceAuthorizationRequest"}}}},"responses":{"200":{"description":"Device and user codes to drive the flow.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceAuthorizationResponse"},"examples":{"default":{"value":{"device_code":"GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS","user_code":"WDJB-MJHT","verification_uri":"https://auth.example.com/device","verification_uri_complete":"https://auth.example.com/device?user_code=WDJB-MJHT","expires_in":1800,"interval":5}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/OAuthErrorResponse"}}}},"/introspect":{"post":{"operationId":"introspect","tags":["OAuth2"],"summary":"OAuth2 token introspection (RFC 7662)","description":"Returns the active state and metadata of a token to a resource server.\nThe caller (a protected resource or confidential client) authenticates\nvia `client_secret_basic`, `client_secret_post`, or `private_key_jwt`.\n","security":[{"clientSecretBasic":[]}],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/AuthorizationHeader"}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/TokenActionRequest"}}}},"responses":{"200":{"description":"Introspection response.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntrospectionResponse"},"examples":{"active":{"value":{"active":true,"scope":"openid profile","client_id":"nauthera_2Rk9","token_type":"Bearer","exp":1735689600,"iat":1735686000,"sub":"248289761001","iss":"https://auth.example.com"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/OAuthErrorResponse"}}}},"/revoke":{"post":{"operationId":"revoke","tags":["OAuth2"],"summary":"OAuth2 token revocation (RFC 7009)","description":"Revokes an access or refresh token. Always returns 200 to the client\n(even for an unknown token) so revocation cannot be used as an oracle.\n","security":[{"clientSecretBasic":[]}],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/AuthorizationHeader"}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/TokenActionRequest"}}}},"responses":{"200":{"description":"Revocation acknowledged (always 200 per RFC 7009).","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/OAuthErrorResponse"}}}},"/userinfo":{"get":{"operationId":"getUserInfo","tags":["OpenID Connect"],"summary":"OpenID Connect UserInfo endpoint","description":"Returns claims for the subject identified by the presented access token\n(OIDC Core §5.3). The access token is presented as a Bearer credential,\nor as a DPoP credential together with a `DPoP` proof (RFC 9449).\n","security":[{"bearerAuth":[]}],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"Claims for the authenticated subject.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Pragma":{"$ref":"#/components/headers/PragmaNoCache"},"Vary":{"$ref":"#/components/headers/VaryAuthorization"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInfo"},"examples":{"default":{"value":{"sub":"248289761001","name":"Jane Doe","email":"jane@example.com","email_verified":true}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/DpopChallenge"}}},"post":{"operationId":"postUserInfo","tags":["OpenID Connect"],"summary":"OpenID Connect UserInfo endpoint (POST)","description":"The POST form of the UserInfo endpoint (OIDC Core §5.3.1).","security":[{"bearerAuth":[]}],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"Claims for the authenticated subject.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Pragma":{"$ref":"#/components/headers/PragmaNoCache"},"Vary":{"$ref":"#/components/headers/VaryAuthorization"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInfo"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/DpopChallenge"}}}},"/end_session":{"get":{"operationId":"endSession","tags":["OpenID Connect"],"summary":"RP-Initiated Logout (OpenID Connect)","description":"Logs the end user out at the OP. If a valid, exactly-registered\n`post_logout_redirect_uri` is supplied, the OP redirects the user agent\nthere with `state` appended; otherwise a logged-out page is shown. The\nOP never redirects to an unregistered URI.\n","security":[],"x-rate-limit":true,"parameters":[{"name":"id_token_hint","in":"query","required":false,"description":"A previously issued ID token identifying the session to end.","schema":{"type":"string"}},{"name":"logout_hint","in":"query","required":false,"schema":{"type":"string"}},{"name":"client_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"post_logout_redirect_uri","in":"query","required":false,"description":"Must exactly match a pre-registered post-logout redirect URI.","schema":{"type":"string","format":"uri"}},{"name":"state","in":"query","required":false,"schema":{"type":"string"}},{"name":"ui_locales","in":"query","required":false,"schema":{"type":"string"}},{"$ref":"#/components/parameters/CookieHeader"}],"responses":{"200":{"description":"The front-channel logout page: one `<iframe>` per participating relying\nparty, plus a redirect to the post-logout target. Served instead of the\n302 only when a participating client registered a\n`frontchannel_logout_uri`.\n\nDelivery through this page is BEST EFFORT and unverifiable — the frames\nare cross-origin, so the OP can read neither their status nor their\nbody, and §4.1 warns that a browser blocking third-party content will\nkeep the RP from seeing its own cookies at all. Back-Channel Logout 1.0\nis the mechanism that actually confirms anything.\n","content":{"text/html":{"schema":{"type":"string"}}},"headers":{"Set-Cookie":{"$ref":"#/components/headers/SetCookie"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Pragma":{"$ref":"#/components/headers/PragmaNoCache"},"Content-Security-Policy":{"description":"Locks the logout page down to inline styles and cross-origin frames.","schema":{"const":"default-src 'none'; style-src 'unsafe-inline'; frame-src https: http:; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"}}}},"302":{"description":"Redirect to the validated post_logout_redirect_uri (or the hosted UI), with the session cookie cleared.","headers":{"Location":{"$ref":"#/components/headers/Location"},"Set-Cookie":{"$ref":"#/components/headers/SetCookie"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Pragma":{"$ref":"#/components/headers/PragmaNoCache"}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/OAuthErrorResponse"}}},"post":{"operationId":"endSessionPost","tags":["OpenID Connect"],"summary":"RP-Initiated Logout, form-encoded (RP-Initiated Logout 1.0 §2)","description":"Identical to the GET form in every respect except where the parameters\ntravel. Preferred by relying parties that do not want an `id_token_hint` in\na URL.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/CookieHeader"}],"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/EndSessionRequest"}}}},"responses":{"200":{"description":"The front-channel logout page (see the GET form).","content":{"text/html":{"schema":{"type":"string"}}},"headers":{"Set-Cookie":{"$ref":"#/components/headers/SetCookie"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Pragma":{"$ref":"#/components/headers/PragmaNoCache"},"Content-Security-Policy":{"description":"Locks the logout page down to inline styles and cross-origin frames.","schema":{"const":"default-src 'none'; style-src 'unsafe-inline'; frame-src https: http:; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"}}}},"302":{"description":"Redirect to the validated post_logout_redirect_uri (or the hosted UI), with the session cookie cleared.","headers":{"Location":{"$ref":"#/components/headers/Location"},"Set-Cookie":{"$ref":"#/components/headers/SetCookie"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Pragma":{"$ref":"#/components/headers/PragmaNoCache"}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/OAuthErrorResponse"}}}},"/check_session_iframe":{"get":{"operationId":"checkSessionIframe","tags":["OpenID Connect"],"summary":"OP session-management iframe (OIDC Session Management 1.0 §3.2)","description":"Serves the OP iframe an RP embeds to poll the end user's login status at\nthe provider without network traffic. It accepts `postMessage` payloads of\nthe form `<client_id> <session_state>` from the RP iframe and posts back\n`unchanged`, `changed`, or `error`.\n\n**Relying parties must append `?client_id=<their client id>`.** §3.2\nrequires the OP iframe to \"enforce that the caller is from an expected\norigin\" and to \"reject postMessage requests from any other source origin\",\nand the only way this page can know which origins are expected is to be\ntold which client is asking: the parameter selects that client's registered\nredirect-URI origins as the page's allowlist.\n\nA page loaded from the bare URL — which is what the `check_session_iframe`\nmetadata value is, since one fixed URL cannot name a client — therefore has\nan empty allowlist and answers nobody. Not knowing which origins are\nexpected is not a license to accept every origin.\n\nA rejected caller receives no reply at all. §3.1 assigns a meaning to each\nof the three replies and none of them means \"you should not be talking to\nme\", and answering would confirm to an unexpected origin that it has found\nthe OP iframe.\n","security":[],"x-rate-limit":true,"parameters":[{"name":"client_id","in":"query","required":false,"description":"The RP whose registered redirect-URI origins become the page's postMessage allowlist, and the only client the page will answer about. Nominally optional, because the metadata value cannot carry one; a page loaded without it answers no origin at all. An unknown client_id is treated the same as none, so this cannot be used to enumerate clients.","schema":{"type":"string","maxLength":256}},{"$ref":"#/components/parameters/CookieHeader"}],"responses":{"200":{"description":"The OP session-management iframe document.","content":{"text/html":{"schema":{"type":"string"}}},"headers":{"Content-Security-Policy":{"description":"Per-response policy carrying the script nonce and a framable frame-ancestors.","schema":{"type":"string"}},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Pragma":{"$ref":"#/components/headers/PragmaNoCache"}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/OAuthErrorResponse"}}}},"/register":{"post":{"operationId":"registerClient","tags":["Client Registration"],"summary":"Dynamic client registration (RFC 7591)","description":"Registers a new client and returns its credentials plus a\n`registration_access_token` and `registration_client_uri` for managing\nthe registration via RFC 7592.\n","security":[{"initialAccessToken":[]}],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientMetadata"},"examples":{"default":{"value":{"redirect_uris":["https://app.example.com/callback"],"token_endpoint_auth_method":"client_secret_basic","grant_types":["authorization_code","refresh_token"],"response_types":["code"],"client_name":"Example App","scope":"openid profile email"}}}}}},"responses":{"201":{"description":"The client was registered.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Pragma":{"$ref":"#/components/headers/PragmaNoCache"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientInformationResponse"}}}},"400":{"$ref":"#/components/responses/RegistrationErrorResponse"},"401":{"$ref":"#/components/responses/RegistrationErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/RegistrationErrorResponse"}}}},"/register/{client_id}":{"get":{"operationId":"getClientConfiguration","tags":["Client Registration"],"summary":"Read a client registration (RFC 7592)","description":"Returns the current registration. Requires the registration access token.","security":[{"registrationAccessToken":[]}],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/ClientIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"}],"responses":{"200":{"description":"The current client registration.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Pragma":{"$ref":"#/components/headers/PragmaNoCache"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientInformationResponse"}}}},"401":{"$ref":"#/components/responses/RegistrationErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/RegistrationErrorResponse"}}},"put":{"operationId":"updateClientConfiguration","tags":["Client Registration"],"summary":"Update a client registration (RFC 7592)","description":"Replaces the client's metadata. The body MUST contain the complete\nmetadata set (not a partial patch) and MUST NOT include server-issued\nfields (registration_access_token, registration_client_uri,\nclient_id_issued_at, client_secret_expires_at).\n","security":[{"registrationAccessToken":[]}],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/ClientIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientMetadata"}}}},"responses":{"200":{"description":"The updated client registration.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Pragma":{"$ref":"#/components/headers/PragmaNoCache"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientInformationResponse"}}}},"401":{"$ref":"#/components/responses/RegistrationErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/RegistrationErrorResponse"}}},"delete":{"operationId":"deleteClientConfiguration","tags":["Client Registration"],"summary":"Deprovision a client registration (RFC 7592)","description":"Deletes the client and revokes its issued tokens. Returns 204.","security":[{"registrationAccessToken":[]}],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/ClientIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"}],"responses":{"204":{"description":"The client was deleted.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Pragma":{"$ref":"#/components/headers/PragmaNoCache"}}},"401":{"$ref":"#/components/responses/RegistrationErrorResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/RegistrationErrorResponse"}}}},"/v1/auth/sign-in":{"post":{"operationId":"signIn","tags":["Interactive"],"summary":"Password sign-in (hosted auth UI)","description":"Verifies a user's email + password with layered brute-force defense and,\non success, establishes an interactive login session (Set-Cookie) and\nreturns `redirectTo`: the /authorize continuation the browser follows so a\nsingle server path issues the authorization code (and later clients reuse\nthe session). The body ALSO carries the preserved OAuth authorization\nparameters, which the server RE-VALIDATES against the client registry —\nthe SPA-supplied `redirect_uri`/`scope` are never trusted. Failures return\na uniform problem+json (401 invalid credentials, 429 throttled/locked) so\nthe endpoint cannot be used to enumerate accounts.\n\nAn account an administrator suspended or blocked is refused only after\nits password verifies. By default the refusal is the wrong password's\n401, identical in status and body. A deployment that reveals lockouts\n(`auth.brute_force.reveal_lockout`) answers 403 `account_unavailable`\ninstead, and only to the caller who presented the right password.\n\nWith a captcha configured, a sign-in for an address, or from a caller,\nthat failed `auth.captcha.sign_in_after` times recently must carry a\n`captcha` solution, and is refused with 400 `captcha_failed` until it\ndoes.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"},{"$ref":"#/components/parameters/UserAgentHeader"},{"$ref":"#/components/parameters/AcceptLanguageHeader"},{"$ref":"#/components/parameters/CookieHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignInRequest"}}}},"responses":{"200":{"description":"Authentication succeeded (a session was established) or a next step is required.","headers":{"Set-Cookie":{"$ref":"#/components/headers/SetCookie"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthStepResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/register":{"post":{"operationId":"registerUser","tags":["Interactive"],"summary":"Local account registration (hosted auth UI)","description":"Creates a local user with an argon2id-hashed password when self-service\nregistration is enabled (config `auth.registration.enabled`; gate this in\nproduction). The username is set to the lowercased email so distinct\nemails never collide on the username uniqueness constraint. Like sign-in,\nthe body carries the preserved OAuth parameters and the response may\nestablish a session and return the /authorize continuation. When the\ndeployment asks for a captcha (`auth.captcha.provider`), `captcha` must\ncarry a solution of a challenge from `GET /v1/auth/captcha`.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"},{"$ref":"#/components/parameters/UserAgentHeader"},{"$ref":"#/components/parameters/CookieHeader"},{"$ref":"#/components/parameters/AcceptLanguageHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterUserRequest"}}}},"responses":{"200":{"description":"The account was created (and optionally signed in).","headers":{"Set-Cookie":{"$ref":"#/components/headers/SetCookie"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthStepResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/captcha":{"get":{"operationId":"getCaptchaChallenge","tags":["Interactive"],"summary":"The captcha the hosted UI must solve (hosted auth UI)","description":"Says which captcha this deployment asks for (`auth.captcha.provider`).\nFor the built-in proof-of-work provider it hands out a fresh challenge\nthe ALTCHA widget solves: `parameters` and `signature` are its wire\nformat, so the widget can fetch this URL directly. For a hosted provider\nit names the site key the provider's widget is shown with. The solution\ngoes back in the `captcha` field of the request the captcha guards, which\nrefuses a missing, wrong, expired or already used one with 400\n`captcha_failed`, and answers 503 `unavailable` when a hosted provider\ncannot be asked. Issuing a challenge stores nothing.\n","security":[],"x-rate-limit":true,"responses":{"200":{"description":"The configured provider and, for `altcha`, a challenge.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptchaChallenge"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/password-reset":{"post":{"operationId":"requestPasswordReset","tags":["Interactive"],"summary":"Ask for a password-reset link (hosted auth UI)","description":"Emails a single-use link to set a new password to the account holding\n`email` in this organization, when there is one and the organization has\nan email relay. The answer is 202 whether or not there is: the account is\nlooked up, and the link queued, after the response, so neither the\nstatus, the body nor the time taken tells whether the address has an\naccount here. The link works for `auth.password_reset.validity`, and an\naccount is sent at most `auth.password_reset.max_requests` within that\ntime; the requests count toward the per-address sign-in throttle. With a\ncaptcha configured, the request must carry a `captcha` solution; a\nrefusal, 400 `captcha_failed`, is the same whatever the address.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"},{"$ref":"#/components/parameters/AcceptLanguageHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetRequest"}}}},"responses":{"202":{"description":"The request was taken. A link is on its way if the address has an account here.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/password-reset/confirm":{"post":{"operationId":"confirmPasswordReset","tags":["Interactive"],"summary":"Set a new password from a reset link (hosted auth UI)","description":"Sets the password of the account a reset link was sent to, and spends\nthe link and every other one its account holds. A link that was never\nissued, has expired, was already used or belongs to another\norganization is refused alike, 400 `invalid_reset_link`, and a password\nthe schema refuses leaves the link unspent. Setting it ends the\naccount's sessions and the grants issued under them, and clears a\nlockout from failed sign-ins, as an administrator's password reset\ndoes. Requests count toward the per-address sign-in throttle.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetConfirmRequest"}}}},"responses":{"204":{"description":"The password was set. The user signs in with it.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/invitation/accept":{"post":{"operationId":"acceptInvitation","tags":["Interactive"],"summary":"Choose a password from an invitation link (hosted auth UI)","description":"Sets the password of the account an administrator invited, verifies the\naddress the invitation was sent to, and spends that link and every other\ninvitation the account holds. A link that was never issued, has expired,\nwas already used, belongs to another organization, or was sent to an\naccount that has a password by now or holds another address is refused\nalike, 400 `invalid_invitation_link`, and a password the schema refuses\nleaves the link unspent. The account has no session yet: the invitee signs in with\nthe new password. Requests count toward the per-address sign-in\nthrottle.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationAcceptRequest"}}}},"responses":{"204":{"description":"The password was set. The invitee signs in with it.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/verify-email/resend":{"post":{"operationId":"requestEmailVerification","tags":["Interactive"],"summary":"Ask for a link that verifies an email address (hosted auth UI)","description":"Emails a single-use link that verifies the address to the account\nholding `email` in this organization, when there is one, its address is\nnot yet verified, and the organization has an email relay. The answer is\n202 whether or not there is: the account is looked up, and the link\nqueued, after the response, so neither the status, the body nor the time\ntaken tells whether the address has an account here. The link works for\n`auth.email_verification.validity`, and an account is sent at most\n`auth.email_verification.max_requests` within that time; the requests\ncount toward the per-address sign-in throttle. With a captcha\nconfigured, the request must carry a `captcha` solution; a refusal, 400\n`captcha_failed`, is the same whatever the address.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"},{"$ref":"#/components/parameters/AcceptLanguageHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerificationRequest"}}}},"responses":{"202":{"description":"The request was taken. A link is on its way if the address has an unverified account here.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/verify-email/confirm":{"post":{"operationId":"confirmEmailVerification","tags":["Interactive"],"summary":"Verify an email address from a link (hosted auth UI)","description":"Marks the address a verification link was sent to as verified, and\nspends the link and every other verification link its account holds. A\nlink that was never issued, has expired, was already used, belongs to\nanother organization, or was sent to an address the account no longer\nholds is refused alike, 400 `invalid_verification_link`. Requests count\ntoward the per-address sign-in throttle.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerificationConfirmRequest"}}}},"responses":{"204":{"description":"The address is verified.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/federation/{slug}/start":{"get":{"operationId":"federationStart","tags":["Interactive"],"summary":"Begin a sign-in through an identity provider","description":"Sends the browser to the organization's identity provider `slug` (OpenID\nConnect Core 1.0 §3.1.2.1) with a state, a nonce and a PKCE challenge, and\nsets a short-lived cookie binding the flow to this browser. The query\ncarries the preserved authorization parameters, as the sign-in body does;\n`intent` says which button was pressed.\n\nThe client named by `client_id` must list the provider. Anything that\nstops the flow here (an unknown provider, a client that does not offer\nit, a provider whose discovery document cannot be read) redirects back to\nthe sign-in page with `federation_error`.\n","security":[],"x-rate-limit":true,"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z][a-z0-9-]*$"}},{"name":"intent","in":"query","required":false,"schema":{"type":"string","enum":["sign_in","sign_up"]}},{"name":"client_id","in":"query","required":false,"schema":{"type":"string","maxLength":256}},{"name":"ui_locales","in":"query","required":false,"schema":{"type":"string","maxLength":256}},{"name":"redirect_uri","in":"query","required":false,"schema":{"type":"string","maxLength":2048}},{"name":"response_type","in":"query","required":false,"schema":{"type":"string","maxLength":32}},{"name":"scope","in":"query","required":false,"schema":{"type":"string","maxLength":2048}},{"name":"state","in":"query","required":false,"schema":{"type":"string","maxLength":1024}},{"name":"nonce","in":"query","required":false,"schema":{"type":"string","maxLength":1024}},{"name":"code_challenge","in":"query","required":false,"schema":{"type":"string","maxLength":128}},{"name":"code_challenge_method","in":"query","required":false,"schema":{"type":"string","maxLength":16}},{"name":"login_hint","in":"query","required":false,"schema":{"type":"string","maxLength":512}},{"name":"prompt","in":"query","required":false,"schema":{"type":"string","maxLength":64}},{"name":"request_uri","in":"query","required":false,"schema":{"type":"string","maxLength":512}},{"name":"resource","in":"query","required":false,"schema":{"type":"string","maxLength":2048}},{"name":"authorization_details","in":"query","required":false,"schema":{"type":"string","maxLength":8192}},{"name":"dpop_jkt","in":"query","required":false,"schema":{"type":"string","maxLength":43}},{"name":"continue_path","in":"query","required":false,"schema":{"type":"string","maxLength":128}},{"name":"max_age","in":"query","required":false,"schema":{"type":"integer","format":"int64","minimum":0}}],"responses":{"302":{"description":"To the provider, or back to the sign-in page with `federation_error`.","headers":{"Location":{"$ref":"#/components/headers/Location"},"Set-Cookie":{"$ref":"#/components/headers/SetCookie"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/federation/{slug}/callback":{"get":{"operationId":"federationCallback","tags":["Interactive"],"summary":"The identity provider's redirect back","description":"Takes the flow's state back once, redeems `code` at the provider and\nverifies its ID token (OpenID Connect Core 1.0 §3.1.3.7). The verified\nsubject signs in as the account linked to it; failing that, as an\nexisting account holding its address when the provider is trusted for\nemail and says the address is verified; failing that, as a new account\nwhen the client allows sign-up through the provider and the organization\naccepts sign-up.\n\nOn success it establishes the session and redirects to the\nauthorization request's continuation. Every refusal redirects to the\nsign-in page with the preserved parameters and `federation_error`.\n","security":[],"x-rate-limit":true,"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z][a-z0-9-]*$"}},{"name":"state","in":"query","required":false,"schema":{"type":"string","maxLength":1024}},{"name":"code","in":"query","required":false,"schema":{"type":"string","maxLength":4096}},{"name":"error","in":"query","required":false,"schema":{"type":"string","maxLength":256}},{"$ref":"#/components/parameters/UserAgentHeader"},{"$ref":"#/components/parameters/AcceptLanguageHeader"},{"$ref":"#/components/parameters/CookieHeader"}],"responses":{"302":{"description":"To the authorization request's continuation, or back to the sign-in page.","headers":{"Location":{"$ref":"#/components/headers/Location"},"Set-Cookie":{"$ref":"#/components/headers/SetCookie"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/sign-out":{"post":{"operationId":"signOut","tags":["Interactive"],"summary":"Sign out of the interactive session (hosted auth UI)","description":"Revokes the current interactive login session (server-side) and clears the\nsession cookie. Returns 200 whether or not a session existed, so it never\nsignals that.\n\nTwo answers are not 200, because each would otherwise tell the user they\nare signed out while their session is still live: a 503 when the session\nstore could not say who is signed in, and a 500 when it refused to revoke\nthe session. Neither clears the cookie, and neither depends on any session\nbut the one the request's own cookie names.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/CookieHeader"},{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"}],"responses":{"200":{"description":"Signed out (idempotent).","headers":{"Set-Cookie":{"$ref":"#/components/headers/SetCookie"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthStepResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/logout/confirm":{"post":{"operationId":"confirmLogout","tags":["Interactive"],"summary":"Answer an RP-initiated logout confirmation (RP-Initiated Logout 1.0 §2)","description":"Completes or abandons a logout parked by `/end_session`. The handle is\nsingle-use: the parked request is consumed whether the user confirms or\ndeclines, so a handle left in browser history cannot sign anyone out later.\n\nOn confirmation the session is revoked, participating relying parties are\nnotified, and `redirectTo` is the already-validated\n`post_logout_redirect_uri` with `state` echoed. On refusal nothing is\nrevoked and `redirectTo` is the provider's own page — §3 forbids the\npost-logout redirect when the logout did not happen.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/CookieHeader"},{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutConfirmRequest"}}}},"responses":{"200":{"description":"The answer was recorded; `redirectTo` says where to go next.","headers":{"Set-Cookie":{"$ref":"#/components/headers/SetCookie"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutConfirmResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/consent":{"post":{"operationId":"consentPrompt","tags":["Interactive"],"summary":"Describe the pending authorization request (OIDC Core §3.1.2.4)","description":"Returns what the client is asking for, resolved server-side from the\nvalidated authorization request. The SPA renders this and nothing from its\nown URL: a consent screen showing an attacker-supplied application name or\nscope list is a phishing page.\n\nIt is a POST rather than a GET because it carries the authorization request\nparameters in a body, keeping them out of the URL, browser history and\nReferer headers. It changes no state.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"},{"$ref":"#/components/parameters/CookieHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentPromptRequest"}}}},"responses":{"200":{"description":"The pending request, for display before the decision.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentPromptResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/consent/decision":{"post":{"operationId":"decideConsent","tags":["Interactive"],"summary":"Record the user's consent decision (OIDC Core §3.1.2.4)","description":"Records the grant on approval and returns the /authorize continuation, or\nreturns the client's redirect_uri carrying `access_denied` on refusal\n(RFC 6749 §4.1.2.1: \"the resource owner denied the request\").\n\nThe authorization request is re-validated against the client registry here;\nthe SPA-supplied parameters are never trusted.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"},{"$ref":"#/components/parameters/CookieHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentDecisionRequest"}}}},"responses":{"200":{"description":"The decision was recorded.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentDecisionResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/device/verify":{"post":{"operationId":"verifyDeviceUserCode","tags":["Interactive"],"summary":"Look up a device authorization by its user code (RFC 8628 §3.3)","description":"Resolves a user-entered `user_code` so the hosted UI can show WHAT is being\nauthorized (the client and the requested scope) before the user decides.\nIt makes no decision itself.\n\nThe `user_code` is a credential (§5.1): its entropy is low enough that the\nendpoint is only safe behind rate limiting, which `x-rate-limit` applies.\nA wrong or malformed code is answered identically to an expired one, so the\nendpoint cannot be used to probe which codes exist.\n\n§5.1 also asks for an attempt budget, which the per-address request limit\ncannot provide: five wrong codes per signed-in user per code lifetime, keyed\nto the user so changing source address does not refill it. Spending it\nanswers `429` with the problem code `too_many_attempts` — terminal, not a\ndelay, and carrying no `Retry-After`: the flow is over and the person must\nstart again at their device. A correct code presented after the budget is\nspent is not served either; the session it names is ended.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"},{"$ref":"#/components/parameters/CookieHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceVerifyRequest"}}}},"responses":{"200":{"description":"The pending device authorization, for display before the decision.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceVerifyResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/device/decision":{"post":{"operationId":"decideDeviceAuthorization","tags":["Interactive"],"summary":"Approve or deny a device authorization (RFC 8628 §3.3)","description":"Records the signed-in user's decision for a pending device authorization.\nApproval binds the user's identity and authentication facts to the device\ncode, which the device's next `/token` poll then redeems; denial makes that\npoll return `access_denied`.\n\nThere is no auto-approval path: §3.3 requires the user to approve, and a\ndevice grant approved without a decision would let anyone who can display a\ncode obtain a token for whoever scans it.\n\nThis endpoint takes a `user_code` too, so it spends the same §5.1 attempt\nbudget as `verifyDeviceUserCode` and answers a spent one the same way — a\n`429` with the problem code `too_many_attempts`. Without that, a sweep of\nthe code space would simply move here.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"},{"$ref":"#/components/parameters/CookieHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceDecisionRequest"}}}},"responses":{"200":{"description":"The decision was recorded.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceDecisionResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/mfa/totp/enroll":{"get":{"operationId":"beginTotpEnrollment","tags":["Interactive"],"summary":"Begin (or re-display) TOTP enrollment","description":"Returns the show-once enrollment payload for the current partial-auth\nsession: the otpauth:// URI (for a QR code) and the Base32 secret (for\nmanual entry). A first call mints a pending secret; repeat calls\nidempotently re-display the SAME secret until it is activated. The secret\nis returned only in the response body over HTTPS (never in a URL) and the\nresponse is marked no-store. GET, not POST, precisely so the secret is\nnever placed in a query string.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/CookieHeader"},{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"}],"responses":{"200":{"description":"The enrollment payload (shown once).","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpEnrollmentResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/mfa/totp/activate":{"post":{"operationId":"activateTotpEnrollment","tags":["Interactive"],"summary":"Confirm and activate TOTP enrollment","description":"Confirms the pending factor by verifying the first code, activates it, and\nreturns the single-use recovery codes exactly ONCE. It does NOT elevate the\nsession — the flow parks at the recovery-codes step until they are\nacknowledged (recovery-ack), so the codes can never be skipped.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/CookieHeader"},{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaCodeRequest"}}}},"responses":{"200":{"description":"The factor was activated; recovery codes returned once.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoveryCodesResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/mfa/totp/recovery-ack":{"post":{"operationId":"acknowledgeRecoveryCodes","tags":["Interactive"],"summary":"Acknowledge saved recovery codes and complete enrollment","description":"The user's one-time \"I saved these\" acknowledgement after activation. It\nelevates the pending session to a full login session and returns the\n/authorize continuation. Until this is called the flow stays at the\nrecovery-codes step.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/CookieHeader"},{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"},{"$ref":"#/components/parameters/UserAgentHeader"},{"$ref":"#/components/parameters/AcceptLanguageHeader"}],"responses":{"200":{"description":"Enrollment complete; the session is elevated.","headers":{"Set-Cookie":{"$ref":"#/components/headers/SetCookie"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthStepResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/mfa/totp/verify":{"post":{"operationId":"verifyTotpChallenge","tags":["Interactive"],"summary":"Verify a TOTP code during sign-in (step-up)","description":"Verifies a TOTP code against the user's active factor for the current\npartial-auth session. The request carries ONLY the code; the OAuth\ncontinuation is read from the server-side pending-auth handle, never the\nclient. On success the session is elevated and `redirectTo` resumes the\nflow. Failures are uniform (401 invalid, 429 locked/throttled).\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/CookieHeader"},{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"},{"$ref":"#/components/parameters/UserAgentHeader"},{"$ref":"#/components/parameters/AcceptLanguageHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaCodeRequest"}}}},"responses":{"200":{"description":"The code was accepted; the session is elevated.","headers":{"Set-Cookie":{"$ref":"#/components/headers/SetCookie"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthStepResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/mfa/recovery-code/verify":{"post":{"operationId":"verifyRecoveryCode","tags":["Interactive"],"summary":"Redeem a single-use recovery code during sign-in","description":"Redeems one single-use recovery code for the current partial-auth session\n— the escape hatch when the authenticator is unavailable. On success the\ncode is consumed, the session is elevated, and `redirectTo` resumes the\nflow. Failures are uniform with the TOTP challenge.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/CookieHeader"},{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"},{"$ref":"#/components/parameters/UserAgentHeader"},{"$ref":"#/components/parameters/AcceptLanguageHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoveryCodeRequest"}}}},"responses":{"200":{"description":"The recovery code was accepted; the session is elevated.","headers":{"Set-Cookie":{"$ref":"#/components/headers/SetCookie"},"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthStepResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/mfa/recovery-codes":{"post":{"operationId":"regenerateRecoveryCodes","tags":["Interactive"],"summary":"Regenerate recovery codes","description":"Re-authenticates with a current TOTP code, then voids the existing\nrecovery-code set and issues a fresh one, returned exactly once. Requires\nan elevated session.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/CookieHeader"},{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaCodeRequest"}}}},"responses":{"200":{"description":"A fresh recovery-code set (shown once); the old set is voided.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoveryCodesResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/mfa":{"get":{"operationId":"getMfaStatus","tags":["Interactive"],"summary":"MFA status for the current session","description":"Reports the user's MFA enrollment status for settings screens: whether a\nfactor is enrolled, the enrolled method kinds, and how many recovery codes\nremain. Requires an elevated session.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/CookieHeader"},{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"}],"responses":{"200":{"description":"The MFA status.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Vary":{"$ref":"#/components/headers/VaryCookie"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaStatusResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/auth/mfa/totp":{"delete":{"operationId":"disableTotp","tags":["Interactive"],"summary":"Disable (remove) the TOTP factor","description":"Removes the user's TOTP factor after re-authenticating with a current code,\nand voids the associated recovery codes. Requires an elevated session.\nUnder a mandatory-MFA policy the user is forced to re-enroll on the next\nsign-in.\n","security":[],"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/CookieHeader"},{"$ref":"#/components/parameters/OriginHeader"},{"$ref":"#/components/parameters/SecFetchSiteHeader"},{"$ref":"#/components/parameters/ContentTypeJsonHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaCodeRequest"}}}},"responses":{"204":{"description":"The factor was removed."},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProblemResponse"}}}},"/v1/admin/orgs":{"get":{"operationId":"adminListOrganizations","tags":["Admin"],"summary":"List organizations (admin, platform staff)","description":"Returns one page of organizations (orgs) using opaque cursor (keyset)\npagination. Bound the page size with `limit` and choose an ordering with\n`sort` (see `x-sort-fields` for what this endpoint accepts; the org id is\nappended so the ordering is total). Then walk the listing by passing the\nprevious response's `page.next_cursor` back as `cursor` for as long as\n`page.has_more` is true. Defaults to id order, which for this server's\ntime-ordered ids is creation order; `display_name` orders by the name\ncase-insensitively. `q` narrows the listing to the organizations whose\nslug or display name starts with it, case-insensitively; a cursor carries\nthe search it was issued for. Requires the `nauthera.orgs:read` scope and\nthe `orgs.read` permission held through a platform role in the main org:\nthe listing spans every organization, so a tenant's members, who hold\n`orgs.read` for their own organization, are refused.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.orgs:read"]}],"x-permission":{"name":"orgs.read","recent_sign_in":false},"x-rate-limit":true,"x-sort-fields":["created_at","display_name","id","slug"],"parameters":[{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/SortQueryParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"name":"q","in":"query","required":false,"description":"Narrow the listing to the organizations whose slug or display name starts with this text, compared case-insensitively. Every character is matched literally. A cursor is bound to the search it was issued for.","schema":{"type":"string","minLength":2,"maxLength":100}}],"responses":{"200":{"description":"One page of organizations.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationList"}}}},"400":{"description":"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, or a `q` outside its declared length.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}},"post":{"operationId":"adminCreateOrganization","tags":["Admin"],"summary":"Create an organization (admin, main-only)","description":"Creates a new organization (org): generates its ES256 signing key,\nregisters it in the runtime registry, and provisions its admin clients. The\none-time admin M2M client secret is returned in the response. Requires the\n`orgs.write` permission, which only a platform role held in the main org\ngrants.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.orgs:write"]}],"x-permission":{"name":"orgs.write","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrganizationRequest"}}}},"responses":{"201":{"description":"The organization was created.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Organization"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"409":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}":{"get":{"operationId":"adminGetOrganization","tags":["Admin"],"summary":"Get an organization (admin)","description":"Returns one organization by slug. Requires the `nauthera.orgs:read` scope\nand the `orgs.read` permission. A caller holding it in an organization\nreads that organization; reading any other takes a platform role held in\nthe main org. Any other organization is refused with the same 403 as one\nthat does not exist, so the answer confirms nothing about which slugs are\ntaken; only platform staff are told 404 for a slug nobody holds.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.orgs:read"]}],"x-permission":{"name":"orgs.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"The organization.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Organization"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}},"patch":{"operationId":"adminUpdateOrganization","tags":["Admin"],"summary":"Update an organization (admin, main-only)","description":"Changes the settings of an organization that may change after creation: the\ndisplay name, FAPI mode and the verified-address requirement. Requires the\n`orgs.write` permission, which only a platform role held in the main org\ngrants.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.orgs:write"]}],"x-permission":{"name":"orgs.write","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOrganizationRequest"}}}},"responses":{"200":{"description":"The updated organization.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Organization"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"409":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}},"delete":{"operationId":"adminDeleteOrganization","tags":["Admin"],"summary":"Delete an organization (admin, main-only)","description":"Deletes an organization by slug (idempotent: deleting an unknown slug is a\n204). The main org cannot be deleted. Requires the `orgs.write`\npermission, which only a platform role held in the main org grants.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.orgs:write"]}],"x-permission":{"name":"orgs.write","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"204":{"description":"The organization was deleted (or was already absent)."},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"409":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/theme":{"get":{"operationId":"adminGetOrganizationTheme","tags":["Admin"],"summary":"Get an organization's dashboard theme (admin)","description":"Returns the theme the admin dashboard wears for one organization: the\nreduced subset of its default branding with its admin console client's\nbranding document laid over it (see DashboardTheme). Requires the `nauthera.orgs:read` scope and the\n`orgs.read` permission, admitted as reading the organization is: a member\nreads their own organization's theme, and platform staff any\norganization's. Any other organization is refused with the same 403 as\none that does not exist. The theme is no secret — the dashboard's shell\ncarries it before anyone signs in — but it is read here because it belongs\nto the admin API. Its entity tag names the theme by its content, so a caller\nholding it asks with `If-None-Match` and is answered 304 while it is\ncurrent; like every admin response it is never stored by HTTP caches.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.orgs:read"]}],"x-permission":{"name":"orgs.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/IfNoneMatchHeader"}],"responses":{"200":{"description":"The organization's dashboard theme.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"ETag":{"$ref":"#/components/headers/ETag"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardTheme"}}}},"304":{"description":"The theme the caller holds, named in If-None-Match, is current.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"ETag":{"$ref":"#/components/headers/ETag"}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/mail":{"get":{"operationId":"adminGetOrganizationMail","tags":["Admin"],"summary":"Get an organization's email relay (admin)","description":"Returns the SMTP relay the organization's email is sent through, read-only:\nits own (configured in the deployment, Helm `mailOrganizations`), the\ndeployment's when it has none, or none. Reports whether credentials are\nconfigured but never reads or returns them. Requires the\n`nauthera.orgs:read` scope and the `orgs.read` permission, admitted as\nreading the organization is.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.orgs:read"]}],"x-permission":{"name":"orgs.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"The organization's email relay.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationMail"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/mail/test":{"post":{"operationId":"adminSendOrganizationTestMail","tags":["Admin"],"summary":"Send a test email through an organization's relay (admin)","description":"Sends a test message through the organization's relay, now rather than\nthrough the delivery queue, to the calling administrator's own email\naddress — never to an address in the request, so this cannot mail anyone\nelse — and reports what the relay answered. A relay that refuses is a\nresult (`delivered: false` with the stage and reply), not an error of this\noperation. An organization with no relay, or a caller with no email\naddress, is refused with 409. Requires the `nauthera.orgs:write` scope and\nthe `orgs.write` permission.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.orgs:write"]}],"x-permission":{"name":"orgs.write","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationMailTestRequest"}}}},"responses":{"200":{"description":"What the relay answered.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationMailTestResult"}}}},"400":{"$ref":"#/components/responses/ProblemResponse"},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"409":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/snapshot":{"get":{"operationId":"adminGetOrganizationSnapshot","tags":["Admin"],"summary":"Count an organization's users, sessions and clients (admin)","description":"Returns how many users `slug`'s organization holds in total, how many\nwere created in the window, how many are locked, suspended or blocked\nnow, how many hold an active second factor, how many never signed in and\nhow many are dormant; how many sessions are live; how many clients it\nholds by type, token endpoint authentication method and source; and how\nrecently its users last signed in, bucketed by a day, a week, a month\nand a quarter. Everything is a count: no user, session or client is\nnamed. The lists are the drill-down, under their own permissions.\n\nThe window bounds only the users counted as new; `from` defaults to 30\ndays before `to`, which defaults to now, and both are widened to whole\nminutes and echoed. A window longer than `x-window` allows is refused.\nEvery other figure is judged at `computed_at`.\n\nAnswers are cached per organization and window for at least the\ndashboard's shortest refresh interval, and replicas sharing a cache\ncompute each one once, in three statements. An aggregate runs under a\ndeadline and a per-server concurrency cap; past either it is answered\n503 `busy` with Retry-After rather than queued.\n\nRequires the `nauthera.insights:read` scope and the `insights.read`\npermission. A caller is confined to its own org unless a platform role\nheld in the main org admits it to every org. Reading the snapshot is\nrecorded at the trace level only, so polling adds nothing to the audit\nlog at the levels a deployment usually runs.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.insights:read"]}],"x-permission":{"name":"insights.read","recent_sign_in":false},"x-rate-limit":true,"x-window":{"from":"from","to":"to","max_days":90},"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"name":"from","in":"query","required":false,"description":"Inclusive start of the window the new users are counted in (RFC3339). Defaults to 30 days before `to`.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"description":"Exclusive end of the window (RFC3339). Defaults to now.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"The organization's counts.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationSnapshot"}}}},"400":{"description":"The window was malformed, ended before it started, or spanned more than `x-window` allows.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"The server is computing as many aggregates as it allows at once, or this one did not finish within its deadline (`busy`); retry after Retry-After. Also answered when the snapshot is not enabled (`unavailable`).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/clients":{"get":{"operationId":"adminListClients","tags":["Admin"],"summary":"List an organization's OIDC clients (admin)","description":"Returns one page of the OIDC clients that belong to `slug`'s organization,\nusing opaque cursor (keyset) pagination. Bound the page size with `limit`\nand choose an ordering with `sort` (see `x-sort-fields` for what this\nendpoint accepts; the registration id is appended so the ordering is\ntotal). Then walk the listing by passing the previous response's\n`page.next_cursor` back as `cursor` for as long as `page.has_more` is\ntrue; `page.prev_cursor` steps back the same way. Defaults to client id\norder; `display_name` orders by the name case-insensitively.\n\n**Which clients belong to an organization.** Every client belongs to\nexactly one organization, decided once, when the client is first\nregistered, and never moved by a later write. A client the server\nprovisioned itself (an organization's admin console and machine client)\nor one registered dynamically (RFC 7591) belongs to the organization the\nserver recorded for it at registration. Any other client — one an\noperator registered over the control plane — belongs to the organization\nwhose issuer it was registered under, whatever labels it carries, or to\nthe main organization when that issuer is no organization's.\n\n`q` narrows the listing to the clients whose client id or display name\nstarts with it, case-insensitively. The filters compose as a\nconjunction; a filter taking several values matches any of them. A\ncursor is bound to the search and the filters it was issued for, so send\nthem with it; the order it carries itself.\n\n`display_name_markers` names what about a client's display name is there\nto be misread — a direction override, a control character, invisible\ncharacters, padding, a name past the length the sign-in pages show, or\nmarkup — and is empty for an ordinary name.\n\nRequires the `nauthera.clients:read` scope and the `clients.read`\npermission; the listing is confined to `slug`'s organization. Secrets,\nsecret hashes and registration access tokens are never returned.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.clients:read"]}],"x-permission":{"name":"clients.read","recent_sign_in":false},"x-rate-limit":true,"x-sort-fields":["client_id","created_at","display_name","id"],"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/SortQueryParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"name":"q","in":"query","required":false,"description":"Narrow the listing to the clients whose client id or display name starts with this text, compared case-insensitively. Every character is matched literally. A cursor is bound to the search it was issued for.","schema":{"type":"string","minLength":2,"maxLength":100}},{"name":"type","in":"query","required":false,"description":"Only public clients, which hold no credential, or only confidential ones.","schema":{"$ref":"#/components/schemas/ClientType"}},{"name":"auth_method","in":"query","required":false,"description":"Token endpoint authentication methods, comma-separated.","style":"form","explode":false,"schema":{"type":"array","maxItems":4,"items":{"$ref":"#/components/schemas/ClientAuthMethod"}}},{"name":"grant_type","in":"query","required":false,"description":"Grant types, comma-separated: a client registering any of them matches.","style":"form","explode":false,"schema":{"type":"array","maxItems":4,"items":{"$ref":"#/components/schemas/ClientGrantType"}}},{"name":"source","in":"query","required":false,"description":"How the clients came to be registered, comma-separated.","style":"form","explode":false,"schema":{"type":"array","maxItems":3,"items":{"$ref":"#/components/schemas/ClientSource"}}}],"responses":{"200":{"description":"One page of the organization's clients.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminClientList"}}}},"400":{"description":"The pagination parameters, the search or a filter 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, or a filter value outside its vocabulary.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/clients/{id}":{"get":{"operationId":"adminGetClient","tags":["Admin"],"summary":"Get one OIDC client's registered configuration (admin)","description":"Returns the registered configuration of one of `slug`'s organization's\nOIDC clients, named by its registration id: its redirect and logout\naddresses, scopes, grant and response types, how it authenticates, its\nPKCE policy, token lifetimes and signing algorithms, how it came to be\nregistered, and the labels the organization may see. A client of another\norganization is answered as 404, exactly like an id that does not exist.\n\n**No secret material.** The response says whether the client holds a\nsecret (`has_secret`) and nothing more. The server keeps only a one-way\nhash of a client secret, shown once when it is minted, so there is no\nsecret to return; neither the hash nor the registration access token is\never included either.\n\n**Personal data.** The registration contacts (RFC 7591 §2 `contacts`)\nare returned only to a caller holding `users.pii.read`; without it the\nfield is absent.\n\nA lifetime field is absent when the client registered none and so takes\nthe server's default. Requires the `nauthera.clients:read` scope and the\n`clients.read` permission.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.clients:read"]}],"x-permission":{"name":"clients.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/ClientRegistrationIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"The client's registered configuration.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminClientDetail"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/clients/{id}/usage":{"get":{"operationId":"adminGetClientUsage","tags":["Admin"],"summary":"Count one OIDC client's daily, monthly and all-time users (admin)","description":"Returns how many distinct users obtained a token for one of `slug`'s\norganization's OIDC clients, named by its registration id: in the last\n24 hours (`daily_users`), the last 30 days (`monthly_users`) and since\ncounting began (`all_time_users`). A client of another organization is\nanswered as 404, exactly like an id that does not exist.\n\nA user counts when they obtain a token for the client through a grant\nthat acts for them — the authorization code, a refresh or the device\ngrant — and counts once per window however many tokens they obtain. A\nclient_credentials token acts for no user and counts nobody. The server\nkeeps, per user and client, only when the user first and last obtained\none, the last to within an hour, so a window's edge is exact to within\nan hour; a user or a client deleted takes those notes with them.\n\n`counted_since` is the earliest first use recorded for the client, and\nso the start of what `all_time_users` covers: the server notes uses from\nthe release that introduced this count on, and a use before that is not\ncounted. It is absent when no user has been counted. `as_of` is the\ninstant the windows end at.\n\nThe counts are aggregates and name no user, so they need neither\n`users.read` nor `audit.read`. Requires the `nauthera.insights:read`\nscope and the `insights.read` permission. Reading them is recorded at\nthe trace level only, so a dashboard polling them adds nothing to the\naudit log at the levels a deployment usually runs.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.insights:read"]}],"x-permission":{"name":"insights.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/ClientRegistrationIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"The client's distinct users per window.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminClientUsage"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/identity-providers":{"get":{"operationId":"adminGetIdentityProviders","tags":["Admin"],"summary":"List an organization's identity providers (admin)","description":"Returns every external OpenID Connect provider `slug`'s organization\nlets its users sign in through, ordered by provider slug. Providers are\ndeclared by an operator through the control plane, so an organization\nholds a handful and the list is not paged; nothing here changes one.\n\n**No secret material.** A provider's client secret is stored sealed and\nnever returned: `secret_fingerprint` identifies the sealing in use, and\nchanges exactly when the secret is replaced.\n\nRequires the `nauthera.orgs:read` scope and the `orgs.read` permission.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.orgs:read"]}],"x-permission":{"name":"orgs.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"The organization's identity providers.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminIdentityProviderList"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/users":{"get":{"operationId":"adminListUsers","tags":["Admin"],"summary":"List users (admin)","description":"Returns one page of local users using opaque cursor (keyset) pagination.\nBound the page size with `limit` and choose an ordering with `sort` (see\n`x-sort-fields` for what this endpoint accepts; the user id is appended so\nthe ordering is total). Then walk the listing by passing the previous\nresponse's `page.next_cursor` back as `cursor` for as long as\n`page.has_more` is true; `page.prev_cursor` steps back the same way.\nDefaults to id order, which for this server's time-ordered ids is creation\norder. Requires the `nauthera.users:read` scope and the `users.read`\npermission; the listing is confined to `slug`'s organization. Password\nhashes are never returned.\n\n`q` narrows the listing to the users one of the `match` fields of whom\nstarts with it, case-insensitively; every character is matched\nliterally. The username is a field every caller may match on. The\ndisplay name and the email are personal data: only a caller holding the\n`users.pii.read` permission may match on them, since a prefix search\nwould rebuild them one character at a time. Without `match` a search\nmatches every field the caller may match on; a `match` naming a field\nthe caller may not is refused with 403 rather than answered from the\nothers. A cursor is bound to the search and the fields it was issued\nfor, so send them with it; the order it carries itself.\n\nThe filters narrow the listing further and compose as a conjunction; a\nfilter taking several values matches any of them. `status` and `locked`\nare decided at the moment of the request: a suspension whose end has\npassed counts as `active`, and a lock whose deadline has passed does not\ncount. The time ranges include their start and exclude their end. A user\nwho never signed in has signed in before any instant, so\n`last_sign_in_to` keeps them and `last_sign_in_from` does not. A cursor\nis bound to the filters it was issued for as it is to the search.\n\n`last_login_at` sorts users who never signed in before everyone else\nascending and after everyone else descending, among themselves by id.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.users:read"]}],"x-permission":{"name":"users.read","recent_sign_in":false},"x-rate-limit":true,"x-sort-fields":["created_at","email","id","last_login_at","username"],"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/SortQueryParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"name":"q","in":"query","required":false,"description":"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.","schema":{"type":"string","minLength":2,"maxLength":100}},{"name":"match","in":"query","required":false,"description":"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`.","style":"form","explode":false,"schema":{"type":"array","maxItems":3,"items":{"$ref":"#/components/schemas/UserSearchField"}}},{"name":"status","in":"query","required":false,"description":"Account statuses in force now, comma-separated: a user whose status is any of them matches.","style":"form","explode":false,"schema":{"type":"array","maxItems":3,"items":{"$ref":"#/components/schemas/UserAccountStatus"}}},{"name":"locked","in":"query","required":false,"x-negatable":true,"description":"Only users the automatic lock after repeated wrong passwords refuses now (true), or only users it does not (false).","schema":{"type":"boolean"}},{"name":"second_factor","in":"query","required":false,"x-negatable":true,"description":"Only users with an active second factor (true), or only users without one (false).","schema":{"type":"boolean"}},{"name":"created_from","in":"query","required":false,"description":"Only users created at or after this instant (RFC3339).","schema":{"type":"string","format":"date-time"}},{"name":"created_to","in":"query","required":false,"description":"Only users created before this instant (RFC3339).","schema":{"type":"string","format":"date-time"}},{"name":"last_sign_in_from","in":"query","required":false,"description":"Only users whose last sign-in was at or after this instant (RFC3339). Users who never signed in do not match.","schema":{"type":"string","format":"date-time"}},{"name":"last_sign_in_to","in":"query","required":false,"description":"Only users whose last sign-in was before this instant (RFC3339), including users who never signed in.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"One page of users.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserList"}}}},"400":{"description":"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":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}},"post":{"operationId":"adminCreateUser","tags":["Admin"],"summary":"Create a user (admin)","description":"Creates a local user in `slug`'s organization, hashing the password under\nTHAT organization's password-hashing policy. Requires the `users.create`\npermission. `username` defaults to the lowercased email when omitted.\n\nWith `invite`, the user is created without a password and `email` is sent\na single-use link to choose one, naming the calling administrator. The\norganization must have an email relay: one without is refused 409\n`mail_relay_missing` and nothing is stored. A request with both or neither of\n`password` and `invite` is refused 400 `invalid_request`.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.users:write"]}],"x-permission":{"name":"users.create","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserRequest"}}}},"responses":{"201":{"description":"The user was created.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"409":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/users/{id}":{"get":{"operationId":"adminGetUser","tags":["Admin"],"summary":"Get a user (admin)","description":"Returns one user by id. Requires the `users.read` permission. The id must belong to `slug`'s organization; one that belongs to another is answered as 404, exactly like an id that does not exist.","security":[{"bearerAuth":[],"oauth2":["nauthera.users:read"]}],"x-permission":{"name":"users.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/UserIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"The user.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}},"patch":{"operationId":"adminUpdateUser","tags":["Admin"],"summary":"Update a user (admin)","description":"Partially updates a user: any of email, username, attributes.\nOmitted fields are left unchanged; `attributes`, when present, REPLACES the\nwhole stored map. The password is not changed here (use the password\nsub-resource). Requires the `users.update` permission. The id must belong\nto `slug`'s organization; one that belongs to another is answered as 404,\nexactly like an id that does not exist. A change of email or username is\nrefused 403 `protected_account` for the caller's own account and for an\naccount that is more privileged than the caller; a privileged account\nneeds a caller holding `roles.assign` as well.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.users:write"]}],"x-permission":{"name":"users.update","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/UserIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserRequest"}}}},"responses":{"200":{"description":"The updated user.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"409":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}},"delete":{"operationId":"adminDeleteUser","tags":["Admin"],"summary":"Delete a user (admin)","description":"Deletes a user and cascades to their sessions, MFA factors, recovery codes\nand pending-auth handles. Idempotent: deleting an unknown id returns 204,\nas does an id belonging to an organization other than `slug`'s. Requires\nthe `users.delete` permission. Refused 403 `protected_account` for the\ncaller's own account and for an account that is more privileged than the\ncaller; a privileged account needs a caller holding `roles.assign` as\nwell.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.users:write"]}],"x-permission":{"name":"users.delete","recent_sign_in":true},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/UserIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/JustificationQueryParam"}],"responses":{"204":{"description":"The user was deleted (or was already absent)."},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/users/{id}/password":{"put":{"operationId":"adminSetUserPassword","tags":["Admin"],"summary":"Set a user's password (admin)","description":"Replaces the user's password with a fresh hash, derived under the\npassword-hashing policy of `slug`'s organization, and revokes the target's\nexisting sessions. Requires the `users.credentials` permission. The id\nmust belong to `slug`'s organization; one that belongs to another is\nanswered as 404, exactly like an id that does not exist. Refused 403\n`protected_account` for the caller's own account and for an account that\nis more privileged than the caller; a privileged account needs a caller\nholding `roles.assign` as well.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.users:write"]}],"x-permission":{"name":"users.credentials","recent_sign_in":true},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/UserIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/JustificationQueryParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPasswordRequest"}}}},"responses":{"204":{"description":"The password was set."},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/users/{id}/invitation":{"post":{"operationId":"adminResendUserInvitation","tags":["Admin"],"summary":"Send a user a new invitation (admin)","description":"Emails a user who has no password yet a new single-use link to choose\none, as creating them with `invite` did. Earlier links keep working until\nthey expire or one of them is accepted. Requires the `users.create`\npermission. The id must belong to `slug`'s organization; one that\nbelongs to another is answered as 404, exactly like an id that does not\nexist. Refused 409 `conflict` when the user already has a password, 409\n`mail_relay_missing` when the organization has no email relay, and 429\n`too_many_requests` once the user was sent `auth.invitation.max_requests`\nlinks within `auth.invitation.validity`.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.users:write"]}],"x-permission":{"name":"users.create","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/UserIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInvitationRequest"}}}},"responses":{"204":{"description":"The invitation was queued."},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"409":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/users/{id}/devices":{"get":{"operationId":"adminGetUserDevices","tags":["Admin"],"summary":"List a user's known browsers and networks (admin)","description":"Returns the browsers and networks the user signed in from within the\nserver's retention, most recently seen first: what a new-sign-in alert\ncompares a sign-in with. A browser is named by the browser and system\nits user agent described and a network by its range; both are personal\ndata, so a caller without the `users.pii.read` permission receives the\nlist without them and `withheld` is true.\n\nReading the list is audited. Requires the `nauthera.sessions:read` scope\nand the `sessions.read` permission. The id must belong to `slug`'s\norganization; one that belongs to another is answered as 404, exactly\nlike an id that does not exist.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.sessions:read"]}],"x-permission":{"name":"sessions.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/UserIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"The user's known browsers and networks.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserDeviceList"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/users/{id}/identities":{"get":{"operationId":"adminGetUserIdentities","tags":["Admin"],"summary":"List the identity provider accounts linked to a user (admin)","description":"Returns the upstream accounts that sign in as the user through the\norganization's identity providers, oldest link first. Each names the\nprovider it was made through and the issuer that assigned its subject;\n`active` is false once the provider was deleted, or re-pointed at\nanother issuer, so the link no longer signs anyone in. The subject is\nthe user's personal data, so a caller without the `users.pii.read`\npermission receives the list without it and `withheld` is true.\n\nReading the list is audited. Requires the `nauthera.users:read` scope\nand the `users.read` permission. The id must belong to `slug`'s\norganization; one that belongs to another is answered as 404, exactly\nlike an id that does not exist.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.users:read"]}],"x-permission":{"name":"users.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/UserIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"The user's linked identity provider accounts.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserIdentityList"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/users/{id}/roles":{"get":{"operationId":"adminGetUserRoles","tags":["Admin"],"summary":"List a user's roles (admin)","description":"Returns the roles the user holds in `slug`'s organization, in role\norder, and the roles the caller may grant to or revoke from them.\nRequires the `roles.read` permission. The id must belong to\n`slug`'s organization; one that belongs to another is answered as 404,\nexactly like an id that does not exist.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.roles:read"]}],"x-permission":{"name":"roles.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/UserIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"The user's roles in the organization.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserRoleList"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/users/{id}/roles/{role}":{"put":{"operationId":"adminGrantUserRole","tags":["Admin"],"summary":"Grant a user a role (admin)","description":"Grants the user `role` in `slug`'s organization; a role already held is\nanswered 204 with nothing changed. Requires the `roles.assign`\npermission and a recent sign-in, and grants every role the caller's own\nroles cover, the owner tier included. Refused 403 `protected_account`\nfor the caller's own account, for a platform role outside the main\norganization, where it would grant nothing, and for a role that grants\na permission or platform reach the caller does not hold.\nThe id must belong to `slug`'s organization; one that belongs to\nanother is answered as 404, exactly like an id that does not exist.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.roles:write"]}],"x-permission":{"name":"roles.assign","recent_sign_in":true},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/UserIdPathParam"},{"$ref":"#/components/parameters/RolePathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/JustificationQueryParam"}],"responses":{"204":{"description":"The user holds the role."},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}},"delete":{"operationId":"adminRevokeUserRole","tags":["Admin"],"summary":"Revoke a user's role (admin)","description":"Takes `role` from the user in `slug`'s organization; a role not held is\nanswered 204 with nothing changed. Requires the `roles.assign`\npermission and a recent sign-in, and the same bounds as granting, so\nnever one of the caller's own. Refused 409 `last_role_administrator`\nwhen the user is\nthe organization's last active account holding `roles.assign`, so no\nrevoke leaves an organization with nobody who can grant roles. The id\nmust belong to `slug`'s organization; one that belongs to another is\nanswered as 404, exactly like an id that does not exist.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.roles:write"]}],"x-permission":{"name":"roles.assign","recent_sign_in":true},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/UserIdPathParam"},{"$ref":"#/components/parameters/RolePathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/JustificationQueryParam"}],"responses":{"204":{"description":"The user no longer holds the role."},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"409":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/users/{id}/session-history":{"get":{"operationId":"adminListUserSessionHistory","tags":["Admin"],"summary":"List a user's ended sessions (admin)","description":"Returns one page of the sessions of the user in the path that ended\nwithin the window, newest end first unless `sort` says otherwise, using\nopaque cursor (keyset) pagination. A user's active sessions are\n`adminListSessions` with `user_id`.\n\nA session ended on purpose is kept from the moment it ends; one that\ntimed out is kept as ending when its idle timeout or absolute lifetime\nran out. The user's sessions that have timed out but were not yet swept\nare ended before the page is read, so the history holds every one of\nthem. Ended sessions are kept for the deployment's retention\n(`auth.session.history_retention`).\n\n`from` and `to` bound the end time: `from` inclusive, `to` exclusive.\nOmitting both means the last 30 days; a window wider than the 90 days\n`x-window` allows is refused with a 400, so an older history is read by\nmoving the window. A cursor is bound to the window it was issued for, so\nsend it with it.\n\nThe address, user agent and device are personal data: a caller without\nthe `users.pii.read` permission receives none of them. Reading the\nhistory is audited at trace level, since the dashboard polls it.\nRequires the `nauthera.sessions:read` scope and the `sessions.read`\npermission. The id must belong to `slug`'s organization; one that\nbelongs to another is answered as 404, exactly like an id that does not\nexist.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.sessions:read"]}],"x-permission":{"name":"sessions.read","recent_sign_in":false},"x-rate-limit":true,"x-sort-fields":["ended_at","id"],"x-window":{"from":"from","to":"to","max_days":90},"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/UserIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/SortQueryParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"name":"from","in":"query","required":false,"description":"Inclusive start of the end-time window (RFC3339). Defaults to 30 days before `to`.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"description":"Exclusive end of the end-time window (RFC3339). Defaults to now.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"One page of the user's ended sessions.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminEndedSessionList"}}}},"400":{"description":"The window or the pagination parameters were invalid: a window that ends at or before it starts or is wider than `x-window` allows, a malformed instant, or a cursor that is malformed, foreign or contradicts `sort`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/users/{id}/sessions":{"delete":{"operationId":"adminEndUserSessions","tags":["Admin"],"summary":"End every session of a user (admin)","description":"Signs the user out everywhere: ends each of their sessions, retires\nevery refresh token they were issued until now (device-flow grants and\ngrants that outlived their session included), withdraws any sign-in\nhalf-way through its second factor, and tells every relying party that\ntook part in an ended session over the back channel. Each session is\nkept in the session history as ended by an administrator. A user with\nno session is answered 204 as well.\n\nThe id must belong to `slug`'s organization; one that belongs to\nanother is answered 404, exactly like an id that does not exist.\nRequires the `nauthera.sessions:write` scope, the `sessions.revoke`\npermission and a recent sign-in, and a reason, recorded on the audit\nevent.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.sessions:write"]}],"x-permission":{"name":"sessions.revoke","recent_sign_in":true},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/UserIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/JustificationQueryParam"}],"responses":{"204":{"description":"Every session of the user was ended."},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/sessions":{"get":{"operationId":"adminListSessions","tags":["Admin"],"summary":"List an organization's active sessions (admin)","description":"Returns one page of the sign-in sessions that are active in `slug`'s\norganization, using opaque cursor (keyset) pagination. Bound the page\nsize with `limit` and choose an ordering with `sort` (see\n`x-sort-fields` for what this endpoint accepts; the session id is\nappended so the ordering is total). Then walk the listing by passing the\nprevious response's `page.next_cursor` back as `cursor` for as long as\n`page.has_more` is true; `page.prev_cursor` steps back the same way.\nDefaults to newest sign-in first.\n\n**Which sessions are active.** A session is listed while it would still\nsign its user in: before its absolute expiry, and no longer idle than the\nidle timeout allows — the same rule the server applies when the session\nis presented. A session that has already died is never listed, whether\nor not it has been swept yet. Sessions that have ended are not part of\nthis listing.\n\n`user_id` narrows the listing to one user's sessions and `client_id` to\nthe sessions a client took part in; they compose as a conjunction. A\ncursor is bound to the filters it was issued for, so send them with it;\nthe order it carries itself.\n\nEach session carries its public id — the `sid` its relying parties\nreceive — and never the credential its browser holds or anything derived\nfrom it. The user's display name, and the address and user agent the\nsession was established from, are personal data: a caller without the\n`users.pii.read` permission receives none of them, and each session names\nwhat it withheld in `withheld`.\n\nReading the listing is audited. Requires the `nauthera.sessions:read`\nscope and the `sessions.read` permission; the listing is confined to\n`slug`'s organization.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.sessions:read"]}],"x-permission":{"name":"sessions.read","recent_sign_in":false},"x-rate-limit":true,"x-sort-fields":["created_at","id"],"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/SortQueryParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"name":"user_id","in":"query","required":false,"description":"Only the sessions of the user with this id.","schema":{"type":"string","maxLength":256}},{"name":"client_id","in":"query","required":false,"description":"Only the sessions the client with this OAuth client id took part in.","schema":{"type":"string","maxLength":256}}],"responses":{"200":{"description":"One page of the organization's active sessions.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSessionList"}}}},"400":{"description":"The pagination parameters or a filter 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, or a filter longer than its declared length.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/sessions/{sid}":{"delete":{"operationId":"adminEndSession","tags":["Admin"],"summary":"End one session (admin)","description":"Ends the active session whose public id is `sid`: the user is signed\nout of it, the refresh tokens it issued are retired, and every relying\nparty that took part in it is told over the back channel (OpenID\nConnect Back-Channel Logout 1.0). The session's other relying parties\nand the user's other sessions are not touched. It is kept in the\nsession history as ended by an administrator.\n\nA sid that names no active session of `slug`'s organization is\nanswered 404, whether another organization holds it or not. Ending a\nsession is audited, with the reason given. Requires the\n`nauthera.sessions:write` scope and the `sessions.revoke` permission.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.sessions:write"]}],"x-permission":{"name":"sessions.revoke","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"name":"sid","in":"path","required":true,"description":"The session's public id, as the session listing names it.","schema":{"type":"string","minLength":1,"maxLength":64}},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/JustificationQueryParam"}],"responses":{"204":{"description":"The session was ended."},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/audit-events":{"get":{"operationId":"adminListAuditEvents","tags":["Admin"],"summary":"List audit events (admin)","description":"Returns one page of audit records for the organization, newest first,\nusing opaque cursor (keyset) pagination.\n\n**The time window is bounded, and that is the point.** Records are stored\nin monthly partitions on Postgres, so a query that names a window lets the\ndatabase discard every partition outside it; an unbounded one would scan\nthe entire retention period. Omitting both `from` and `to` therefore means\n\"the last 30 days\" rather than \"everything\", and a window wider than 90\ndays — the bound `x-window` declares — is rejected with a 400; narrow it\nand walk the pages.\n\nFilters compose as a conjunction. The axes a detector keys on are each\nanswered from an index: one value of `reason`, `outcome`, `operation`,\n`actor_type`, `client_net` or `client_id`, and the set\n`outcome=failure,denied,error`, every refusal and fault. Several values of\none filter narrow the window as it is read in time order instead. There\nis no free-text search: the fields worth searching — `action`,\n`category`, `outcome`, `severity`, `reason`, `actor_type` — are closed\nvocabularies with their own parameters, so a substring scan over an\nappend-only table this size is never the answer.\n\n`client_id` finds a client's records whether the server resolved them to\nthe client or the caller only named it — a sign-in form's `client_id`, an\nidentifier a client authentication presented that no client registered —\nand each record says which, by carrying it as `client_id` or as\n`claimed_client_id`.\n\nThis is a page through a window of time, newest first, and not a way to\nfollow the log as it grows: a record can become visible after records\nthat occurred later, so a walk by time skips it. Follow the log with\n`adminTailAuditEvents` instead.\n\nReading the audit log is itself audited, whatever the configured level:\nthe record names whose trail was read — the principal, target, client,\nsession or network filtered for — and whether platform records were in\nview. A query refused for its filters, window or cursor is recorded too.\n\nRequires the `nauthera.audit:read` scope and the `audit.read` permission.\nA caller is confined to its own org unless a platform role held in the\nmain org admits it to every org. A caller who also holds `platform.audit`\nadditionally sees platform-scoped records (process startup, retention,\naudit-pipeline failures), which have no owning tenant and are never shown\nto one.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.audit:read"]}],"x-permission":{"name":"audit.read","recent_sign_in":false},"x-rate-limit":true,"x-sort-fields":["id","occurred_at"],"x-window":{"from":"from","to":"to","max_days":90},"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/SortQueryParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"name":"from","in":"query","required":false,"description":"Inclusive start of the window (RFC3339). Defaults to 30 days before `to`.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"description":"Exclusive end of the window (RFC3339). Defaults to now.","schema":{"type":"string","format":"date-time"}},{"$ref":"#/components/parameters/AuditActionFilterParam"},{"$ref":"#/components/parameters/AuditCategoryFilterParam"},{"$ref":"#/components/parameters/AuditOutcomeFilterParam"},{"$ref":"#/components/parameters/AuditSeverityFilterParam"},{"$ref":"#/components/parameters/AuditReasonFilterParam"},{"$ref":"#/components/parameters/AuditActorTypeFilterParam"},{"$ref":"#/components/parameters/AuditOperationFilterParam"},{"$ref":"#/components/parameters/AuditActorIdFilterParam"},{"$ref":"#/components/parameters/AuditTargetIdFilterParam"},{"$ref":"#/components/parameters/AuditTargetTypeFilterParam"},{"$ref":"#/components/parameters/AuditClientIpFilterParam"},{"$ref":"#/components/parameters/AuditClientNetFilterParam"},{"$ref":"#/components/parameters/AuditClientIdFilterParam"},{"$ref":"#/components/parameters/AuditSessionIdFilterParam"},{"$ref":"#/components/parameters/AuditRequestIdFilterParam"},{"$ref":"#/components/parameters/AuditTraceIdFilterParam"},{"$ref":"#/components/parameters/AuditCrossTenantOnlyFilterParam"}],"responses":{"200":{"description":"One page of audit records.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditEventList"}}}},"400":{"description":"The query was invalid — a time window that ends before it starts or spans more than 90 days, a value outside a filter's vocabulary, a malformed action pattern, a `client_ip` that is not an address or a `client_net` that is not an IPv4 /24 or IPv6 /64, or a pagination parameter that is malformed, foreign, or contradicts `sort`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/audit-events/tail":{"get":{"operationId":"adminTailAuditEvents","tags":["Admin"],"summary":"Follow the audit log (admin)","description":"Returns the organization's audit records in the order they reached the\nstore, from where the last read left off: the read a SIEM or a detector\npolls to follow the log as it grows.\n\n**Why not the list.** A record is stamped with the moment it occurred,\nthen waits in a queue and is written in a batch whose transaction commits\nwhen it commits — a record the store kept refusing is retried until it\nis taken. So a record can become visible after records that occurred, or\nwere persisted, later than it, and a consumer that walks the list by time\nhas already passed its position by the time it appears. Arrival order is\nassigned by the store, and every page of it stops below the oldest write\nstill in progress, so no record can later appear before a position a\nconsumer already holds: a record committed late is returned on a later\npage, after the records the earlier pages held. The price is lag, never\nloss: while any transaction on the database server stays open, the tail\nwaits for it.\n\n**How to follow it.** Omit `cursor` on the first read, which starts at\nthe first record kept in arrival order, or name `from` to leave out the\nrecords that occurred before it. Every response carries `next_cursor`, whether\nits page was full, short or empty; pass it back as `cursor`, with the same\nfilters and `from`. Read again at once while `has_more` is true, and wait\nbefore reading again once it is false. The cursor is opaque and bound to\nthe organization and filters of the read that issued it.\n\nThe filters are the list's. There is no window: the tail is bounded by\nits position instead, and the server finds each partition's next records\nwith one index seek for each organization read — the organization's own\nrecords, and the platform's beside them when they are in view. A record\nthat occurred before `from` is never returned, however late it arrives.\n\nA position stays valid when the database is restored or migrated into\nanother cluster: the server notices at boot that the records it holds\nare ahead of the new cluster's order and carries the order on after\nthem, so a consumer resumes from the cursor it held.\n\nEvery read is itself audited, like every other read of the log. A\nconsumer that does not filter by `action` therefore reads, on each poll,\nthe record of the poll before it.\n\nRequires the `nauthera.audit:read` scope and the `audit.read` permission,\nunder the same tenancy rules as the list.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.audit:read"]}],"x-permission":{"name":"audit.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"$ref":"#/components/parameters/LimitQueryParam"},{"name":"cursor","in":"query","required":false,"description":"Where to resume: the `next_cursor` of the previous read, verbatim. Omit it to start at the beginning. A cursor issued for another organization, other filters or another `from`, or by another listing, is rejected with a 400.","schema":{"type":"string","maxLength":1024}},{"name":"from","in":"query","required":false,"description":"Only records that occurred at or after this instant (RFC3339). Keep it the same for the whole tail.","schema":{"type":"string","format":"date-time"}},{"$ref":"#/components/parameters/AuditActionFilterParam"},{"$ref":"#/components/parameters/AuditCategoryFilterParam"},{"$ref":"#/components/parameters/AuditOutcomeFilterParam"},{"$ref":"#/components/parameters/AuditSeverityFilterParam"},{"$ref":"#/components/parameters/AuditReasonFilterParam"},{"$ref":"#/components/parameters/AuditActorTypeFilterParam"},{"$ref":"#/components/parameters/AuditOperationFilterParam"},{"$ref":"#/components/parameters/AuditActorIdFilterParam"},{"$ref":"#/components/parameters/AuditTargetIdFilterParam"},{"$ref":"#/components/parameters/AuditTargetTypeFilterParam"},{"$ref":"#/components/parameters/AuditClientIpFilterParam"},{"$ref":"#/components/parameters/AuditClientNetFilterParam"},{"$ref":"#/components/parameters/AuditClientIdFilterParam"},{"$ref":"#/components/parameters/AuditSessionIdFilterParam"},{"$ref":"#/components/parameters/AuditRequestIdFilterParam"},{"$ref":"#/components/parameters/AuditTraceIdFilterParam"},{"$ref":"#/components/parameters/AuditCrossTenantOnlyFilterParam"}],"responses":{"200":{"description":"One page of the audit log in arrival order.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditEventTail"}}}},"400":{"description":"The query was invalid — a value outside a filter's vocabulary, a malformed action pattern, a `client_ip` or `client_net` that is not an address or a /24 or /64 network, or a cursor that is malformed or was issued for another tail.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/audit-events/counts":{"get":{"operationId":"adminCountAuditEvents","tags":["Admin"],"summary":"Count audit events over time (admin)","description":"Returns how many audit records match, per time bucket and optionally per\nvalue of one column, as a dense, zero-filled UTC series. It takes exactly\nthe audit list's filters, so the counts are the records the list pages\nthrough for the same parameters — except that reads of the audit log\nitself are left out unless `include_audit_reads` is set, so a\ndashboard's own reads do not dominate the chart drawn from them.\n\n`bucket` is one of a fixed list, each allowed up to a window: `1m` up to\n2 hours, `10m` up to 2 days, `1h` up to 31 days and `1d` up to the 90-day\nmaximum `x-window` declares. Omitted, it is the finest the window allows.\nThe window is widened to whole buckets and echoed. A window longer than\nthe bucket asked for allows is refused with `window_too_large`, and an\nanswer of more than 32768 counts — series times buckets — with\n`too_many_series`.\n\nAnswers are cached per organization, read scope, permission projection,\nfilters, bucket and window for at least the dashboard's shortest refresh\ninterval, and replicas sharing a cache compute each one once. An\naggregate runs under a deadline and a per-server concurrency cap; past\neither it is answered 503 `busy` with Retry-After rather than queued.\n\nRequires the `nauthera.insights:read` scope and the `insights.read`\npermission. Every filter that names a principal, a session or a request\n— `actor_id`, `target_id`, `session_id`, `request_id`, `trace_id` — and\ngrouping by `action` additionally require `audit.read`, and `client_ip`\nalso `users.pii.read`, so counts never become a per-user or per-address\nrecord for a caller who may see only aggregates. Reading counts is\nrecorded at the trace level only, so polling adds nothing to the audit\nlog at the levels a deployment usually runs.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.insights:read"]}],"x-permission":{"name":"insights.read","recent_sign_in":false},"x-rate-limit":true,"x-window":{"from":"from","to":"to","max_days":90},"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"name":"bucket","in":"query","required":false,"description":"The bucket length. Omitted, the finest the window allows.","schema":{"$ref":"#/components/schemas/AuditCountBucket"}},{"name":"group_by","in":"query","required":false,"description":"The column to split the counts by. Omitted, one series.","schema":{"$ref":"#/components/schemas/AuditCountGroup"}},{"name":"include_audit_reads","in":"query","required":false,"description":"Count reads of the audit log too.","schema":{"type":"boolean"}},{"name":"from","in":"query","required":false,"description":"Inclusive start of the window (RFC3339). Defaults to 30 days before `to`.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"description":"Exclusive end of the window (RFC3339). Defaults to now.","schema":{"type":"string","format":"date-time"}},{"name":"action","in":"query","required":false,"description":"Action patterns, comma-separated. `*` and `?` are wildcards and do not stop at a dot, so `user.*` covers `user.password.changed` as well as `user.created`. A pattern matching no known action simply returns nothing, and an empty element is ignored. With no pattern, every action but `system.audit.checkpoint` is counted: the hash chain's checkpoint records, which the verifier reads, are counted only for a pattern that matches them.","style":"form","explode":false,"x-match":"glob","schema":{"type":"array","maxItems":32,"items":{"type":"string","maxLength":128,"pattern":"^[a-z0-9_.*?-]*$"}}},{"name":"category","in":"query","required":false,"description":"Categories, comma-separated.","style":"form","explode":false,"schema":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/AuditCategory"}}},{"name":"outcome","in":"query","required":false,"description":"Outcomes, comma-separated.","style":"form","explode":false,"schema":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/AuditOutcome"}}},{"name":"severity","in":"query","required":false,"description":"Severities, comma-separated.","style":"form","explode":false,"schema":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/AuditSeverity"}}},{"name":"actor_id","in":"query","required":false,"description":"Only records caused by this principal.","schema":{"type":"string","maxLength":256}},{"name":"target_id","in":"query","required":false,"description":"Only records acting on this object.","schema":{"type":"string","maxLength":256}},{"name":"target_type","in":"query","required":false,"schema":{"$ref":"#/components/schemas/AuditTargetType"}},{"name":"client_ip","in":"query","required":false,"schema":{"type":"string","maxLength":64}},{"name":"client_id","in":"query","required":false,"schema":{"type":"string","maxLength":256}},{"name":"session_id","in":"query","required":false,"schema":{"type":"string","maxLength":256}},{"name":"request_id","in":"query","required":false,"description":"The `X-Request-ID` of a single request. This and `trace_id` are what a support engineer arrives with.","schema":{"type":"string","maxLength":256}},{"name":"trace_id","in":"query","required":false,"schema":{"type":"string","maxLength":64}},{"name":"cross_tenant_only","in":"query","required":false,"description":"Only actions taken on this organization by an actor from another one. It is the single most useful security view a multi-tenant audit log offers, which is why it is a first-class filter.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"The counts.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditEventCounts"}}}},"400":{"description":"The query was invalid: a malformed window, an unknown vocabulary value or pattern, a window longer than the bucket allows (`window_too_large`), or an answer that would carry too many series (`too_many_series`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"The server is computing as many aggregates as it allows at once, or this one did not finish within its deadline (`busy`); retry after Retry-After. Also answered when audit logging is not enabled (`unavailable`).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/audit-events/top":{"get":{"operationId":"adminRankAuditEvents","tags":["Admin"],"summary":"Rank the subjects behind audit events (admin)","description":"Returns the subjects behind the audit records the filters match, by how\nmany each caused: the principals (`by=actor`), the objects acted on\n(`by=target`, the accounts a run of refused sign-ins resolved to), the\nclients (`by=client`, resolved or only named, as the `client_id` filter\nmatches them) or the addresses (`by=client_ip`). It takes the audit\nlist's filters, so a ranking names exactly the records the list pages\nthrough for the same parameters, and reads of the audit log itself are\nleft out unless `include_audit_reads` is set.\n\nA ranking groups every record in its window in one pass, so it is bounded\nwhere the counts are not: the window spans at most the 31 days `x-window`\ndeclares, refused past that with `window_too_large`, and at most 25\nsubjects come back. Answers are cached per organization, read scope,\npermission projection, filters, subject, limit and window for at least\nthe dashboard's shortest refresh interval, and replicas sharing a cache\ncompute each one once; past the aggregate deadline or the per-server cap\na ranking is answered 503 `busy` with Retry-After.\n\nRequires the `nauthera.audit:read` scope and the `audit.read` permission:\na ranked list of who is behind a set of records is the audit list's\nknowledge in another shape. Ranking by address, and filtering by one,\nadditionally require `users.pii.read`, since an address is personal data;\nwithout it an actor's or target's display snapshot is withheld and only\nthe id is returned. Reading a ranking is recorded at the trace level\nonly, so polling adds nothing to the audit log at the levels a deployment\nusually runs.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.audit:read"]}],"x-permission":{"name":"audit.read","recent_sign_in":false},"x-rate-limit":true,"x-window":{"from":"from","to":"to","max_days":31},"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"name":"by","in":"query","required":true,"description":"What the records are grouped by.","schema":{"$ref":"#/components/schemas/AuditRankSubject"}},{"name":"limit","in":"query","required":false,"description":"How many subjects to return (1–25; default 10).","schema":{"type":"integer","minimum":1,"maximum":25}},{"name":"include_audit_reads","in":"query","required":false,"description":"Count reads of the audit log too.","schema":{"type":"boolean"}},{"name":"from","in":"query","required":false,"description":"Inclusive start of the window (RFC3339). Defaults to 31 days before `to`.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"description":"Exclusive end of the window (RFC3339). Defaults to now.","schema":{"type":"string","format":"date-time"}},{"name":"action","in":"query","required":false,"description":"Action patterns, comma-separated. `*` and `?` are wildcards and do not stop at a dot, so `user.*` covers `user.password.changed` as well as `user.created`. A pattern matching no known action simply returns nothing, and an empty element is ignored. With no pattern, every action but `system.audit.checkpoint` is counted: the hash chain's checkpoint records, which the verifier reads, are counted only for a pattern that matches them.","style":"form","explode":false,"x-match":"glob","schema":{"type":"array","maxItems":32,"items":{"type":"string","maxLength":128,"pattern":"^[a-z0-9_.*?-]*$"}}},{"name":"category","in":"query","required":false,"description":"Categories, comma-separated.","style":"form","explode":false,"schema":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/AuditCategory"}}},{"name":"outcome","in":"query","required":false,"description":"Outcomes, comma-separated.","style":"form","explode":false,"schema":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/AuditOutcome"}}},{"name":"severity","in":"query","required":false,"description":"Severities, comma-separated.","style":"form","explode":false,"schema":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/AuditSeverity"}}},{"name":"actor_id","in":"query","required":false,"description":"Only records caused by this principal.","schema":{"type":"string","maxLength":256}},{"name":"target_id","in":"query","required":false,"description":"Only records acting on this object.","schema":{"type":"string","maxLength":256}},{"name":"target_type","in":"query","required":false,"schema":{"$ref":"#/components/schemas/AuditTargetType"}},{"name":"client_ip","in":"query","required":false,"schema":{"type":"string","maxLength":64}},{"name":"client_id","in":"query","required":false,"schema":{"type":"string","maxLength":256}},{"name":"session_id","in":"query","required":false,"schema":{"type":"string","maxLength":256}},{"name":"request_id","in":"query","required":false,"description":"The `X-Request-ID` of a single request. This and `trace_id` are what a support engineer arrives with.","schema":{"type":"string","maxLength":256}},{"name":"trace_id","in":"query","required":false,"schema":{"type":"string","maxLength":64}},{"name":"cross_tenant_only","in":"query","required":false,"description":"Only actions taken on this organization by an actor from another one. It is the single most useful security view a multi-tenant audit log offers, which is why it is a first-class filter.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"The ranking.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditEventRanking"}}}},"400":{"description":"The query was invalid: a malformed window, an unknown vocabulary value or pattern, or a window longer than 31 days (`window_too_large`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"The server is computing as many aggregates as it allows at once, or this one did not finish within its deadline (`busy`); retry after Retry-After. Also answered when audit logging is not enabled (`unavailable`).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/audit-events/{id}":{"get":{"operationId":"adminGetAuditEvent","tags":["Admin"],"summary":"Get an audit event (admin)","description":"Returns one audit record by id, subject to the same tenancy rules as the\nlisting: a record belonging to another organization reads as missing, so a\ncaller cannot use this to probe for one.\n\nIds are time-ordered (UUIDv7), so the server derives the window the record\nmust lie in from the id itself and reads a single partition rather than\nsearching the whole retention period.\n\nThe read is audited with the id it asked for, found or not.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.audit:read"]}],"x-permission":{"name":"audit.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuditEventIdPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"The audit record.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditEvent"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"404":{"$ref":"#/components/responses/ProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/audit-catalog":{"get":{"operationId":"adminGetAuditCatalog","tags":["Admin"],"summary":"Get the audit event catalog (admin)","description":"Returns every audit event type this server can emit, with its category,\ndefault severity, verbosity level, whether configuration can suppress it,\nits declared metadata keys, and its translation key.\n\nThe vocabularies themselves are enumerated in this document, so filter\ncontrols and translation coverage can be built statically without calling\nthis endpoint. What it adds is the per-action detail an enum cannot carry,\nand the set THIS BUILD actually emits — which is what a client should check\nagainst when the deployment it is talking to may be older or newer than the\ncontract it was generated from.\n\nThe catalog is a compile-time constant of the server, so its CONTENT is\nidentical for every organization and a client may hold it for the lifetime\nof a build. The response itself is `no-store` and varies on the credential\nall the same: what a caller receives here is 200-or-401 depending on the\ntoken presented, so a shared cache that stored the 200 would hand the\ncatalog to a caller who presented nothing.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.audit:read"]}],"x-permission":{"name":"audit.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"The audit event catalog.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Vary":{"$ref":"#/components/headers/VaryAuthorization"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditCatalog"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/orgs/{slug}/threats":{"get":{"operationId":"adminGetThreatOverview","tags":["Admin"],"summary":"Get the threat overview (admin)","description":"Returns every signal of the threat catalog counted over the window: the\nfailed sign-ins, locks and throttles that show credentials being\nguessed, the second-factor failures and locks, the grants and tokens\nrefused at the protocol endpoints, the clients that failed to prove who\nthey are, the registrations and names that look built to mislead, the\nprivileged calls refused, and the malformed traffic turned away at the\nedge. Each signal carries its count, the count of the equal window\nbefore, its counts per bucket, the status that judges them against the\nsignal's recommended threshold, the audit list filters that list exactly\nthe records it counted, and the frameworks that describe the threat.\nBeside them, the shape of the window's failed sign-ins — how many, from\nhow many addresses, against how many accounts — says whether one account\nis being hammered, many are being sprayed from one address, or a leaked\nlist is being tried from many.\n\nEverything is an aggregate: no address, account or client is named. The\ndrill-down is the audit list, under its own permissions.\n\n`bucket` is one of the audit counts' fixed list, each allowed up to a\nwindow: `1m` up to 2 hours, `10m` up to 2 days, `1h` up to 31 days and\n`1d` up to the 90-day maximum `x-window` declares. Omitted, it is the\nfinest the window allows, and the window is widened to whole buckets\nand echoed. A window longer than the bucket asked for allows is refused\nwith `window_too_large`.\n\nAnswers are cached per organization, bucket and window for at least the\ndashboard's shortest refresh interval, and replicas sharing a cache\ncompute each one once. An aggregate runs under a deadline and a\nper-server concurrency cap; past either it is answered 503 `busy` with\nRetry-After rather than queued.\n\nRequires the `nauthera.threats:read` scope and the `threats.read`\npermission. A caller is confined to its own org unless a platform role\nheld in the main org admits it to every org. Reading the overview is\nrecorded at the trace level only, so polling adds nothing to the audit\nlog at the levels a deployment usually runs.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.threats:read"]}],"x-permission":{"name":"threats.read","recent_sign_in":false},"x-rate-limit":true,"x-window":{"from":"from","to":"to","max_days":90},"parameters":[{"$ref":"#/components/parameters/OrgSlugPathParam"},{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"},{"name":"bucket","in":"query","required":false,"description":"The bucket length. Omitted, the finest the window allows.","schema":{"$ref":"#/components/schemas/AuditCountBucket"}},{"name":"from","in":"query","required":false,"description":"Inclusive start of the window (RFC3339). Defaults to 30 days before `to`.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"description":"Exclusive end of the window (RFC3339). Defaults to now.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Every signal over the window, judged.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThreatOverview"}}}},"400":{"description":"The query was invalid — a time window that ends before it starts or spans more than 90 days, or longer than the bucket asked for allows (`window_too_large`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"The server is computing as many aggregates as it allows at once, or this one did not finish within its deadline (`busy`); retry after Retry-After. Also answered when audit logging is not enabled (`unavailable`).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}},"/v1/admin/deployment/client-ip":{"get":{"operationId":"adminGetClientIPPosture","tags":["Admin"],"summary":"Get how the client address is determined (admin)","description":"Returns where this deployment reads the client address from\n(`security.client_ip`), the proxies it trusts and which of those are\nwider than they need to be, the trusted proxies that forwarded recent\nrequests, and, when recent traffic looks like it arrives through a\nreverse proxy the configuration does not name, what was seen and the\nvalues that would read the client instead.\n\nThe finding is judged by the replica that answers, over the requests it\nserved most recently, and never changes what the server believes. It is\ndeployment configuration, so only a platform role held in the main\norganization may read it. Requires the `nauthera.insights:read` scope and\nthe `insights.read` permission.\n","security":[{"bearerAuth":[],"oauth2":["nauthera.insights:read"]}],"x-permission":{"name":"insights.read","recent_sign_in":false},"x-rate-limit":true,"parameters":[{"$ref":"#/components/parameters/AuthorizationHeader"},{"$ref":"#/components/parameters/DpopHeader"}],"responses":{"200":{"description":"The client-address configuration and any proxy finding.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"Vary":{"$ref":"#/components/headers/VaryAuthorization"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientIPPosture"}}}},"401":{"$ref":"#/components/responses/ProtectedProblemResponse"},"403":{"$ref":"#/components/responses/ProtectedProblemResponse"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/ProtectedProblemResponse"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"A bearer (or DPoP) access token issued by this provider."},"clientSecretBasic":{"type":"http","scheme":"basic","description":"Confidential-client credentials via HTTP Basic (client_secret_basic)."},"registrationAccessToken":{"type":"http","scheme":"bearer","description":"The RFC 7592 registration access token for client management."},"initialAccessToken":{"type":"http","scheme":"bearer","description":"The RFC 7591 §1.2 initial access token: an OAuth 2.0 access token carrying the `nauthera.clients:register` scope, which authorizes calls to the client registration endpoint and supplies the organization the new client is bound to. RFC 7591 §3 makes open (unauthenticated) registration a SHOULD; this deployment deviates deliberately, because every client here belongs to an org and an anonymous registrant has none."},"oauth2":{"type":"oauth2","description":"OAuth 2.0 access tokens used to authorize the admin REST surface\n(`/v1/admin/**`). Two flows are advertised, both minting tokens against\nthis provider's `/token` endpoint:\n\n- **authorizationCode** (PKCE, RFC 7636) — an interactive operator signs\n  in to their organization's admin-console client (a public PKCE client\n  auto-provisioned per org) and receives an access token carrying the\n  granted OAuth scopes (below). The caller's roles are read from\n  `user_org_roles` on every request, not taken from the token.\n- **clientCredentials** (RFC 6749 §4.4) — a confidential machine-to-machine\n  admin client exchanges its `client_id`/`client_secret` for an access\n  token carrying the scopes its registration grants (and any admin roles\n  bound to it through reserved client labels). Used for headless\n  automation of the admin surface.\n\nAuthorization on every admin operation is the CONJUNCTION of two\nindependent checks: the token's `scope` claim MUST contain the operation's\ndeclared scope (validated via `oauthsrv.HasScope`) AND the caller MUST\nhold the permission the operation declares in its `x-permission`,\nresolved from their roles through a fixed table (`internal/rbac`). A\ntoken that carries the scope but whose caller lacks the permission — or\nthe reverse — is rejected. A scope caps what a client may do on its\nuser's behalf; a permission is what that user may do at all.\n`x-permissions` below is the closed permission vocabulary, and the\nserver's handlers are held to each operation's declaration by a test.\nThe users and audit scopes below are org-scoped: a caller acts only\nwithin its own org unless a platform role held in the main org admits it\nto every org. The clients, sessions, insights, threats and roles scopes\nare the admin dashboard's, registered by every organization's admin console\nalone; like every scope they cap what a client may ask for, and the\npermission each operation declares does the fine-grained work.\n\n`x-permission` also declares whether the operation requires a recent\nsign-in. A token whose sign-in is older than the deployment allows\n(fifteen minutes by default), states none, or lacks a second factor the\ndeployment requires is answered 401 `insufficient_user_authentication`,\nwith an RFC 9470 challenge carrying `max_age` (and `acr_values`): sign\nin again and replay the call.\n","x-permissions":{"orgs.read":"Read organizations.","orgs.write":"Create, update and delete organizations.","users.read":"Read an organization's users.","users.pii.read":"Read users' personal data, such as email addresses and attributes.","users.create":"Create users.","users.update":"Change a user's email address, username and attributes.","users.delete":"Delete users.","users.lifecycle":"Suspend, block, reactivate and unlock users.","users.credentials":"Set a user's password and manage their second factors.","users.activity":"Read what a user did and what was done to them.","sessions.read":"Read users' sessions.","sessions.revoke":"End users' sessions.","clients.read":"Read an organization's OIDC clients.","audit.read":"Read an organization's audit log.","platform.audit":"Read the platform-scoped audit records, which concern the deployment rather than any one organization, beside an organization's own.","insights.read":"Read an organization's insights and alerts.","threats.read":"Read an organization's threat analytics: the attack signals counted from its audit log, against their recommended thresholds.","roles.read":"Read who holds which role.","roles.assign":"Grant and revoke roles."},"flows":{"authorizationCode":{"authorizationUrl":"https://auth.example.com/authorize","tokenUrl":"https://auth.example.com/token","refreshUrl":"https://auth.example.com/token","scopes":{"openid":"OpenID Connect authentication (issue an ID token).","profile":"Basic profile claims (name, preferred_username, …).","email":"Email-address claims (email, email_verified).","offline_access":"Issue a refresh token for long-lived access.","nauthera.orgs:read":"Read organizations through the admin API (list and get). Pairs with the orgs.read permission: held in an organization, it reads that organization; listing them, and reading any other, takes a platform role held in the main org.","nauthera.orgs:write":"Create, update and delete organizations through the admin API. Pairs with the orgs.write permission, which only a platform role held in the main org grants.","nauthera.users:read":"Read local users through the admin API (list and get). Pairs with the users.read permission; confined to the caller's own org unless a platform role held in the main org admits it to every org.","nauthera.users:write":"Create, update, delete and set passwords for local users through the admin API. Pairs with the users.create, users.update, users.delete or users.credentials permission, whichever the operation declares; confined to the caller's own org unless a platform role held in the main org admits it to every org.","nauthera.audit:read":"Read the audit log through the admin API. Pairs with the audit.read permission; confined to the caller's own org unless a platform role held in the main org admits it to every org. Platform-scoped records are visible only to a caller who also holds platform.audit.","nauthera.clients:read":"Read an organization's OIDC clients through the admin API. Pairs with the clients.read permission. Distinct from nauthera.clients:register, which authorizes dynamic client registration and grants no admin authority.","nauthera.sessions:read":"Read users' sessions through the admin API. Pairs with the sessions.read permission.","nauthera.sessions:write":"End users' sessions through the admin API. Pairs with the sessions.revoke permission.","nauthera.insights:read":"Read an organization's insights and alerts through the admin API. Pairs with the insights.read permission.","nauthera.threats:read":"Read an organization's threat analytics through the admin API. Pairs with the threats.read permission.","nauthera.roles:read":"Read who holds which role through the admin API. Pairs with the roles.read permission.","nauthera.roles:write":"Grant and revoke roles through the admin API. Pairs with the roles.assign permission."}},"clientCredentials":{"tokenUrl":"https://auth.example.com/token","refreshUrl":"https://auth.example.com/token","scopes":{"nauthera.orgs:read":"Read organizations through the admin API (list and get). Pairs with the orgs.read permission: held in an organization, it reads that organization; listing them, and reading any other, takes a platform role held in the main org.","nauthera.orgs:write":"Create, update and delete organizations through the admin API. Pairs with the orgs.write permission, which only a platform role held in the main org grants.","nauthera.users:read":"Read local users through the admin API (list and get). Pairs with the users.read permission; confined to the caller's own org unless a platform role held in the main org admits it to every org.","nauthera.users:write":"Create, update, delete and set passwords for local users through the admin API. Pairs with the users.create, users.update, users.delete or users.credentials permission, whichever the operation declares; confined to the caller's own org unless a platform role held in the main org admits it to every org.","nauthera.audit:read":"Read the audit log through the admin API. Pairs with the audit.read permission; confined to the caller's own org unless a platform role held in the main org admits it to every org. Platform-scoped records are visible only to a caller who also holds platform.audit.","nauthera.clients:read":"Read an organization's OIDC clients through the admin API. Pairs with the clients.read permission. Distinct from nauthera.clients:register, which authorizes dynamic client registration and grants no admin authority.","nauthera.sessions:read":"Read users' sessions through the admin API. Pairs with the sessions.read permission.","nauthera.sessions:write":"End users' sessions through the admin API. Pairs with the sessions.revoke permission.","nauthera.insights:read":"Read an organization's insights and alerts through the admin API. Pairs with the insights.read permission.","nauthera.threats:read":"Read an organization's threat analytics through the admin API. Pairs with the threats.read permission.","nauthera.roles:read":"Read who holds which role through the admin API. Pairs with the roles.read permission.","nauthera.roles:write":"Grant and revoke roles through the admin API. Pairs with the roles.assign permission."}}}}},"schemas":{"AuthorizationServerMetadata":{"type":"object","description":"OAuth 2.0 Authorization Server Metadata (RFC 8414) unified with OpenID Provider Metadata (OIDC Discovery 1.0). Serves both discovery documents.","required":["issuer","authorization_endpoint","token_endpoint","jwks_uri","response_types_supported","subject_types_supported","id_token_signing_alg_values_supported"],"properties":{"issuer":{"type":"string","format":"uri","description":"The issuer identifier (an https URL, no query/fragment/trailing slash).","example":"https://auth.example.com"},"authorization_endpoint":{"type":"string","format":"uri","example":"https://auth.example.com/authorize"},"token_endpoint":{"type":"string","format":"uri","example":"https://auth.example.com/token"},"userinfo_endpoint":{"type":"string","format":"uri","example":"https://auth.example.com/userinfo"},"jwks_uri":{"type":"string","format":"uri","example":"https://auth.example.com/.well-known/jwks.json"},"registration_endpoint":{"type":"string","format":"uri","example":"https://auth.example.com/register"},"introspection_endpoint":{"type":"string","format":"uri","example":"https://auth.example.com/introspect"},"revocation_endpoint":{"type":"string","format":"uri","example":"https://auth.example.com/revoke"},"device_authorization_endpoint":{"type":"string","format":"uri","example":"https://auth.example.com/device_authorization"},"pushed_authorization_request_endpoint":{"type":"string","format":"uri","example":"https://auth.example.com/par"},"end_session_endpoint":{"type":"string","format":"uri","example":"https://auth.example.com/end_session"},"check_session_iframe":{"type":"string","format":"uri","example":"https://auth.example.com/check_session_iframe"},"scopes_supported":{"$ref":"#/components/schemas/StringArray"},"response_types_supported":{"type":"array","items":{"type":"string"},"example":["code"]},"response_modes_supported":{"$ref":"#/components/schemas/StringArray"},"grant_types_supported":{"$ref":"#/components/schemas/StringArray"},"subject_types_supported":{"type":"array","items":{"type":"string"},"example":["public"]},"id_token_signing_alg_values_supported":{"type":"array","items":{"type":"string"},"example":["ES256"]},"userinfo_signing_alg_values_supported":{"$ref":"#/components/schemas/StringArray"},"request_object_signing_alg_values_supported":{"$ref":"#/components/schemas/StringArray"},"token_endpoint_auth_methods_supported":{"$ref":"#/components/schemas/StringArray"},"token_endpoint_auth_signing_alg_values_supported":{"$ref":"#/components/schemas/StringArray"},"revocation_endpoint_auth_methods_supported":{"$ref":"#/components/schemas/StringArray"},"revocation_endpoint_auth_signing_alg_values_supported":{"$ref":"#/components/schemas/StringArray"},"introspection_endpoint_auth_methods_supported":{"$ref":"#/components/schemas/StringArray"},"introspection_endpoint_auth_signing_alg_values_supported":{"$ref":"#/components/schemas/StringArray"},"code_challenge_methods_supported":{"$ref":"#/components/schemas/StringArray"},"dpop_signing_alg_values_supported":{"$ref":"#/components/schemas/StringArray"},"authorization_details_types_supported":{"$ref":"#/components/schemas/StringArray"},"claims_supported":{"$ref":"#/components/schemas/StringArray"},"ui_locales_supported":{"$ref":"#/components/schemas/StringArray"},"acr_values_supported":{"$ref":"#/components/schemas/StringArray"},"claims_parameter_supported":{"type":"boolean","example":false},"request_parameter_supported":{"type":"boolean","example":false},"request_uri_parameter_supported":{"type":"boolean","example":true},"require_pushed_authorization_requests":{"type":"boolean","example":false},"authorization_response_iss_parameter_supported":{"type":"boolean","example":true},"tls_client_certificate_bound_access_tokens":{"type":"boolean","example":false},"backchannel_logout_supported":{"type":"boolean","example":false},"backchannel_logout_session_supported":{"type":"boolean","example":false},"frontchannel_logout_supported":{"type":"boolean","example":false},"frontchannel_logout_session_supported":{"type":"boolean","example":false},"service_documentation":{"type":"string","format":"uri"},"op_policy_uri":{"type":"string","format":"uri"},"op_tos_uri":{"type":"string","format":"uri"}}},"StringArray":{"type":"array","description":"A JSON array of strings.","items":{"type":"string"}},"JWKS":{"type":"object","description":"A JSON Web Key Set.","required":["keys"],"properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/JWK"}}}},"JWK":{"type":"object","description":"A public JSON Web Key (RFC 7517). Two key types are published: EC (kty=EC, ES256/P-256) carries crv/x/y per RFC 7518 §6.2, and RSA (kty=RSA, RS256) carries n/e per RFC 7518 §6.3. The two member sets are mutually exclusive, so only kty/use/kid/alg are required — a key serializes exactly the members its own type defines.","required":["kty","use","kid","alg"],"properties":{"kty":{"type":"string","example":"EC"},"use":{"type":"string","example":"sig"},"kid":{"type":"string","example":"7Xy1t-abc"},"alg":{"type":"string","example":"ES256"},"crv":{"type":"string","example":"P-256"},"x":{"type":"string","example":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU"},"y":{"type":"string","example":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"},"n":{"type":"string","example":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbW"},"e":{"type":"string","example":"AQAB"}}},"Problem":{"type":"object","description":"RFC 9457 (formerly RFC 7807) problem details (application/problem+json). `code` is a stable machine-readable slug for the problem kind (it mirrors the suffix of `type`), so consumers can branch on it without parsing prose. `errors` is present on validation failures and lists every offending field.","required":["title","status"],"properties":{"type":{"type":"string","format":"uri","example":"urn:problem-type:validation_error"},"title":{"type":"string","example":"Request validation failed"},"status":{"type":"integer","example":400},"detail":{"type":"string","example":"One or more fields did not satisfy the API schema."},"code":{"$ref":"#/components/schemas/ProblemCode"},"instance":{"type":"string","example":"/introspect"},"request_id":{"type":"string","example":"8d94ed49-1caf-4ab3-9594-557142f2d023"},"trace_id":{"type":"string","description":"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":{"type":"array","description":"Field-level violations (present for validation errors).","items":{"$ref":"#/components/schemas/FieldError"}}}},"ProblemCode":{"type":"string","description":"The kind of problem, as a stable machine-readable slug a client branches\non and translates rather than parsing `title` or `detail`, which are\nEnglish prose for logs. A problem whose status says everything the\nserver knows carries no `code`.\n\n`validation_error` is the one code whose message is not a sentence about\nthe code itself: the request failed the schema, and the specifics are\nthe `errors` list — one entry per offending field — which a form renders\non those fields. `required` and `malformed` are spelled like two of the\nper-field violation codes (`FieldError.code`) but mean something else;\nthe two vocabularies are told apart by position, never by spelling.\n\n**Compatibility:** this is a CLOSED enum, with the same cost as\n`AuditAction`: a client generated against an older version of this\ndocument rejects a problem carrying a code added after it was built.\nRegenerate on upgrade, or decode the field as a plain string. It is\nenumerated anyway so that what a client must translate is knowable\nfrom the contract alone.\n","enum":["invalid_request","invalid_credentials","forbidden","unauthorized","insufficient_user_authentication","protected_account","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","invalid_verification_link","invalid_invitation_link","mail_relay_missing","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"]},"FieldError":{"type":"object","description":"A single field-level validation violation.","required":["field","code","message"],"properties":{"field":{"type":"string","description":"Offending field, dotted for nested bodies (e.g. child.id).","example":"token_type_hint"},"code":{"type":"string","description":"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":{"type":"string","example":"field token_type_hint must be one of [access_token, refresh_token]"}}},"OAuthError":{"type":"object","description":"An OAuth2 error response (RFC 6749 §5.2). `iss` (RFC 9207) is present on authorization-endpoint error responses so clients can detect mix-up.","additionalProperties":false,"required":["error"],"properties":{"error":{"type":"string","description":"A single ASCII error code.","example":"invalid_request"},"error_description":{"type":"string","example":"client_id, redirect_uri and response_type are required"},"error_uri":{"type":"string","format":"uri"},"state":{"type":"string","description":"Echoed authorization-request state (RFC 6749 §4.1.2.1)."},"iss":{"type":"string","format":"uri","description":"Issuer identifier on authorization error responses (RFC 9207)."}}},"PushedAuthorizationRequest":{"type":"object","description":"Authorization-request parameters pushed to the PAR endpoint (RFC 9126). Mirrors the /authorize query parameters plus client authentication.","properties":{"request_uri":{"type":"string","maxLength":512,"description":"MUST NOT be sent; present only so the server can reject it."},"response_type":{"type":"string","enum":["code"],"example":"code"},"max_age":{"type":"integer","format":"int64","minimum":0},"client_id":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[!-~]+$","example":"nauthera_2Rk9"},"redirect_uri":{"type":"string","format":"uri","maxLength":2048,"example":"https://app.example.com/callback"},"scope":{"type":"string","maxLength":2048,"pattern":"^\\S+( \\S+)*$","example":"openid profile email"},"state":{"type":"string","maxLength":1024},"nonce":{"type":"string","maxLength":1024},"response_mode":{"type":"string","enum":["query","fragment","form_post"]},"prompt":{"type":"string","maxLength":64,"pattern":"^(none|login|consent|select_account)( (none|login|consent|select_account))*$"},"login_hint":{"type":"string","maxLength":512},"ui_locales":{"type":"string","maxLength":256,"pattern":"^\\S+( \\S+)*$"},"acr_values":{"type":"string","maxLength":512,"pattern":"^\\S+( \\S+)*$"},"claims":{"type":"string","maxLength":8192},"code_challenge":{"type":"string","minLength":43,"maxLength":128,"pattern":"^[A-Za-z0-9_-]+$"},"code_challenge_method":{"type":"string","enum":["S256"]},"request":{"type":"string","maxLength":16384},"authorization_details":{"type":"string","maxLength":8192},"resource":{"type":"string","format":"uri","maxLength":2048},"dpop_jkt":{"type":"string","minLength":43,"maxLength":43,"pattern":"^[A-Za-z0-9_-]+$"},"client_secret":{"type":"string","maxLength":512},"client_assertion_type":{"type":"string","maxLength":128,"example":"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"},"client_assertion":{"type":"string","maxLength":16384}}},"PushedAuthorizationResponse":{"type":"object","description":"A pushed authorization request reference (RFC 9126 §2.2).","required":["request_uri","expires_in"],"properties":{"request_uri":{"type":"string","description":"Single-use reference passed to /authorize.","example":"urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY22c"},"expires_in":{"type":"integer","description":"Lifetime of the request_uri in seconds.","example":90}}},"TokenRequest":{"type":"object","description":"OAuth2 token request parameters (form-encoded). grant_type is semantically required, but validation is left to the token handler so a malformed request returns a spec-shaped OAuth2 error (RFC 6749 §5.2) rather than a generic validation problem. Fields cover the authorization_code, refresh_token, client_credentials, device_code (RFC 8628), token-exchange (RFC 8693) and jwt-bearer (RFC 7523) grants.","properties":{"grant_type":{"type":"string","description":"The grant type. Not constrained by an enum here so an unsupported value yields an OAuth2 `unsupported_grant_type` error from the handler rather than a generic validation problem.","example":"authorization_code"},"code":{"type":"string","example":"SplxlOBeZQQYbYS6WxSbIA"},"redirect_uri":{"type":"string","format":"uri","example":"https://app.example.com/callback"},"code_verifier":{"type":"string","example":"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"},"refresh_token":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string"},"client_assertion_type":{"type":"string","maxLength":128,"example":"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"},"client_assertion":{"type":"string","maxLength":16384},"scope":{"type":"string","example":"openid profile"},"device_code":{"type":"string"},"subject_token":{"type":"string"},"subject_token_type":{"type":"string","example":"urn:ietf:params:oauth:token-type:access_token"},"actor_token":{"type":"string"},"actor_token_type":{"type":"string"},"requested_token_type":{"type":"string"},"audience":{"type":"string"},"resource":{"type":"string","format":"uri"},"authorization_details":{"type":"string"},"assertion":{"type":"string"}}},"TokenResponse":{"type":"object","description":"A successful OAuth2 token response (RFC 6749 §5.1, RFC 8693 §2.2).","required":["access_token","token_type"],"properties":{"access_token":{"type":"string","example":"2YotnFZFEjr1zCsicMWpAA"},"token_type":{"type":"string","description":"`Bearer` or `DPoP` (RFC 9449) for sender-constrained tokens.","example":"Bearer"},"expires_in":{"type":"integer","example":3600},"refresh_token":{"type":"string","example":"tGzv3JOkF0XG5Qx2TlKWIA"},"id_token":{"type":"string"},"scope":{"type":"string","example":"openid profile"},"issued_token_type":{"type":"string","example":"urn:ietf:params:oauth:token-type:access_token"},"authorization_details":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"DeviceAuthorizationRequest":{"type":"object","description":"Device authorization request parameters (RFC 8628 §3.1).","properties":{"client_id":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[!-~]+$","example":"nauthera_2Rk9"},"scope":{"type":"string","maxLength":2048,"pattern":"^\\S+( \\S+)*$","example":"openid profile"},"resource":{"type":"string","format":"uri","maxLength":2048},"dpop_jkt":{"type":"string","description":"JWK SHA-256 thumbprint (base64url, 43 chars) binding the device session to a DPoP key (RFC 9449).","minLength":43,"maxLength":43,"pattern":"^[A-Za-z0-9_-]+$"},"client_secret":{"type":"string","maxLength":512},"client_assertion_type":{"type":"string","maxLength":128},"client_assertion":{"type":"string","maxLength":16384}}},"DeviceAuthorizationResponse":{"type":"object","description":"Device authorization response (RFC 8628 §3.2).","required":["device_code","user_code","verification_uri","expires_in"],"properties":{"device_code":{"type":"string","description":"The device verification code.","example":"GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS"},"user_code":{"type":"string","description":"The end-user code shown on the device.","example":"WDJB-MJHT"},"verification_uri":{"type":"string","format":"uri","description":"Where the user enters the user_code.","example":"https://auth.example.com/device"},"verification_uri_complete":{"type":"string","format":"uri","description":"verification_uri pre-filled with the user_code (for QR codes).","example":"https://auth.example.com/device?user_code=WDJB-MJHT"},"expires_in":{"type":"integer","description":"Lifetime of the device_code / user_code in seconds.","example":1800},"interval":{"type":"integer","description":"Minimum seconds the client must wait between /token polls.","example":5}}},"TokenActionRequest":{"type":"object","description":"Form body for introspection/revocation (RFC 7662 / RFC 7009), including client authentication (client_secret_post / private_key_jwt).","additionalProperties":false,"required":["token"],"properties":{"token":{"type":"string","minLength":1,"maxLength":8192,"example":"2YotnFZFEjr1zCsicMWpAA"},"token_type_hint":{"type":"string","enum":["access_token","refresh_token"],"example":"access_token"},"client_id":{"type":"string","maxLength":256,"pattern":"^[!-~]+$"},"client_secret":{"type":"string","maxLength":512},"client_assertion_type":{"type":"string","maxLength":128},"client_assertion":{"type":"string","maxLength":16384}}},"IntrospectionResponse":{"type":"object","description":"Token introspection response (RFC 7662 §2.2).","required":["active"],"properties":{"active":{"type":"boolean","example":true},"scope":{"type":"string","example":"openid profile"},"client_id":{"type":"string","example":"nauthera_2Rk9"},"username":{"type":"string"},"token_type":{"type":"string","description":"How the token must be presented at a protected resource: `DPoP` when it is sender-constrained to a proof-of-possession key (RFC 9449 §5), `Bearer` otherwise. It always agrees with `cnf` — the two are derived from the same confirmation claim, so a response typed `DPoP` carries a `cnf` and one typed `Bearer` carries none.","example":"Bearer"},"cnf":{"additionalProperties":{"type":"string"},"description":"Confirmation of the key the token is bound to, present only for a sender-constrained token and omitted entirely otherwise. `jkt` is the RFC 7638 SHA-256 thumbprint of the DPoP proof key (RFC 9449 §6.1).","example":{"jkt":"0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I"}},"exp":{"type":"integer","example":1735689600},"iat":{"type":"integer","example":1735686000},"nbf":{"type":"integer"},"sub":{"type":"string","example":"248289761001"},"aud":{"type":"string"},"iss":{"type":"string","format":"uri","example":"https://auth.example.com"},"jti":{"type":"string"},"auth_time":{"type":"integer","description":"When the user signed in for the grant the token was issued from (RFC 9068 §2.2.1), the original sign-in's on a refreshed token. Absent for a token no user signed in for.","example":1735685000},"acr":{"type":"string","description":"The authentication context class that sign-in satisfied (RFC 9068 §2.2.1). Absent when none was recorded."},"amr":{"type":"array","description":"The authentication methods that sign-in used (RFC 8176). Absent when none were recorded.","items":{"type":"string"},"example":["pwd","otp"]}}},"UserInfo":{"type":"object","description":"OpenID Connect standard claims (OIDC Core §5.1).","required":["sub"],"properties":{"sub":{"type":"string","example":"248289761001"},"name":{"type":"string","example":"Jane Doe"},"given_name":{"type":"string"},"family_name":{"type":"string"},"middle_name":{"type":"string"},"nickname":{"type":"string"},"preferred_username":{"type":"string"},"profile":{"type":"string","format":"uri"},"picture":{"type":"string","format":"uri"},"website":{"type":"string","format":"uri"},"email":{"type":"string","format":"email","example":"jane@example.com"},"email_verified":{"type":"boolean","example":true},"gender":{"type":"string"},"birthdate":{"type":"string","example":"1990-01-01"},"zoneinfo":{"type":"string","example":"Europe/Zurich"},"locale":{"type":"string","example":"en-US"},"phone_number":{"type":"string"},"phone_number_verified":{"type":"boolean"},"address":{"$ref":"#/components/schemas/Address"},"updated_at":{"type":"integer","example":1735686000}}},"Address":{"type":"object","description":"OpenID Connect address claim (OIDC Core §5.1.1).","properties":{"formatted":{"type":"string"},"street_address":{"type":"string"},"locality":{"type":"string"},"region":{"type":"string"},"postal_code":{"type":"string"},"country":{"type":"string"}}},"EndSessionRequest":{"type":"object","description":"The RP-Initiated Logout parameters (RP-Initiated Logout 1.0 §2), carried in a form body instead of the query string. Identical in meaning to the GET form's query parameters; sending them here keeps an id_token_hint — which identifies the user — out of browser history, access logs and Referer headers.","properties":{"id_token_hint":{"type":"string","maxLength":8192,"description":"A previously issued ID token identifying the session to end."},"logout_hint":{"type":"string","maxLength":1024},"client_id":{"type":"string","maxLength":256},"post_logout_redirect_uri":{"type":"string","maxLength":2048,"description":"Must exactly match a pre-registered post-logout redirect URI."},"state":{"type":"string","maxLength":1024},"ui_locales":{"type":"string","maxLength":256}}},"ClientMetadata":{"type":"object","description":"Client metadata for dynamic registration (RFC 7591 §2, with OpenID Connect Dynamic Client Registration extensions).","properties":{"client_id":{"type":"string","maxLength":256,"description":"RFC 7592 §2.2 requires an UPDATE (PUT) body to carry the client's own `client_id`, and the server MUST reject a value that differs from the currently issued one. On registration (POST) it is ignored: a client never influences its own identifier (RFC 9700 §4.15.1)."},"client_secret":{"type":"string","maxLength":512,"description":"RFC 7592 §2.2: if present on an UPDATE it MUST match the currently issued secret; a client may never overwrite its secret with a value of its own choosing. Ignored on registration."},"redirect_uris":{"type":"array","description":"Redirection URIs (REQUIRED for redirect-based grants).","minItems":1,"maxItems":32,"uniqueItems":true,"items":{"type":"string","format":"uri","maxLength":2048},"example":["https://app.example.com/callback"]},"token_endpoint_auth_method":{"type":"string","enum":["none","client_secret_basic","client_secret_post","private_key_jwt","tls_client_auth","self_signed_tls_client_auth"],"example":"client_secret_basic"},"token_endpoint_auth_signing_alg":{"type":"string","enum":["ES256","RS256"],"example":"ES256"},"grant_types":{"type":"array","maxItems":16,"uniqueItems":true,"items":{"type":"string","maxLength":128},"example":["authorization_code","refresh_token"]},"response_types":{"type":"array","maxItems":8,"uniqueItems":true,"items":{"type":"string","maxLength":64},"example":["code"]},"client_name":{"type":"string","maxLength":256,"example":"Example App"},"client_uri":{"type":"string","format":"uri","maxLength":2048},"logo_uri":{"type":"string","format":"uri","maxLength":2048},"scope":{"type":"string","maxLength":2048,"pattern":"^\\S+( \\S+)*$","example":"openid profile email"},"contacts":{"type":"array","maxItems":32,"uniqueItems":true,"items":{"type":"string","format":"email","maxLength":256}},"tos_uri":{"type":"string","format":"uri","maxLength":2048},"policy_uri":{"type":"string","format":"uri","maxLength":2048},"jwks_uri":{"type":"string","format":"uri","maxLength":2048},"jwks":{"$ref":"#/components/schemas/JWKS"},"software_id":{"type":"string","format":"uuid"},"software_version":{"type":"string","maxLength":128},"software_statement":{"type":"string","maxLength":16384},"application_type":{"type":"string","enum":["web","native"]},"subject_type":{"type":"string","enum":["public","pairwise"]},"sector_identifier_uri":{"type":"string","format":"uri","maxLength":2048},"id_token_signed_response_alg":{"type":"string","maxLength":16},"userinfo_signed_response_alg":{"type":"string","maxLength":16},"request_object_signing_alg":{"type":"string","maxLength":16},"default_max_age":{"type":"integer","minimum":0,"maximum":315360000},"require_auth_time":{"type":"boolean"},"default_acr_values":{"type":"array","maxItems":16,"uniqueItems":true,"items":{"type":"string","maxLength":256}},"initiate_login_uri":{"type":"string","format":"uri","maxLength":2048},"post_logout_redirect_uris":{"type":"array","maxItems":32,"uniqueItems":true,"items":{"type":"string","format":"uri","maxLength":2048}},"backchannel_logout_uri":{"type":"string","format":"uri","maxLength":2048},"backchannel_logout_session_required":{"type":"boolean"},"frontchannel_logout_uri":{"type":"string","format":"uri","maxLength":2048},"frontchannel_logout_session_required":{"type":"boolean"}}},"ClientInformationResponse":{"type":"object","description":"Client information response (RFC 7591 §3.2.1 + RFC 7592). Echoes the registered metadata and adds the server-issued credentials and management fields.","required":["client_id"],"properties":{"client_id":{"type":"string","example":"nauthera_2Rk9"},"client_secret":{"type":"string","description":"Present for confidential clients only."},"client_id_issued_at":{"type":"integer","example":1735686000},"client_secret_expires_at":{"type":"integer","description":"Epoch seconds, or 0 if the secret never expires.","example":0},"registration_access_token":{"type":"string","description":"RFC 7592 token authorizing management of this registration."},"registration_client_uri":{"type":"string","format":"uri","description":"RFC 7592 management endpoint for this client.","example":"https://auth.example.com/register/nauthera_2Rk9"},"redirect_uris":{"type":"array","items":{"type":"string","format":"uri"}},"token_endpoint_auth_method":{"type":"string"},"token_endpoint_auth_signing_alg":{"type":"string"},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}},"client_name":{"type":"string"},"client_uri":{"type":"string","format":"uri"},"logo_uri":{"type":"string","format":"uri"},"scope":{"type":"string"},"contacts":{"type":"array","items":{"type":"string"}},"tos_uri":{"type":"string","format":"uri"},"policy_uri":{"type":"string","format":"uri"},"jwks_uri":{"type":"string","format":"uri"},"software_id":{"type":"string"},"software_version":{"type":"string"},"application_type":{"type":"string"},"post_logout_redirect_uris":{"type":"array","items":{"type":"string","format":"uri"}},"id_token_signed_response_alg":{"type":"string"},"backchannel_logout_uri":{"type":"string","format":"uri"},"backchannel_logout_session_required":{"type":"boolean"},"frontchannel_logout_uri":{"type":"string","format":"uri"},"frontchannel_logout_session_required":{"type":"boolean"}}},"RegistrationError":{"type":"object","description":"A dynamic client registration error (RFC 7591 §3.2.2).","additionalProperties":false,"required":["error"],"properties":{"error":{"type":"string","enum":["invalid_redirect_uri","invalid_client_metadata","invalid_software_statement","unapproved_software_statement","invalid_token","access_denied","temporarily_unavailable"],"example":"invalid_client_metadata"},"error_description":{"type":"string","example":"token_endpoint_auth_method is not supported"}}},"SignInRequest":{"type":"object","description":"A hosted-UI password sign-in. `email` + `password` are the credentials; the remaining fields are the preserved OAuth authorization parameters the server re-validates against the client registry (never trusted as-is). The field set is exhaustively enumerated so it stays in lockstep with the SPA's authQuery.PRESERVED_PARAMS.","additionalProperties":false,"required":["email","password"],"properties":{"email":{"type":"string","description":"The login identifier (email). Normalized (lowercased) server-side.","minLength":1,"maxLength":320,"example":"alice@example.com"},"password":{"type":"string","minLength":1,"maxLength":4096},"remember":{"type":"boolean","description":"Request a longer-lived session (advisory)."},"captcha":{"type":"string","maxLength":4096,"description":"The captcha solution, required once the address typed or the caller's address failed `auth.captcha.sign_in_after` times recently: the sign-in is refused with `captcha_failed` until it carries one."},"client_id":{"type":"string","maxLength":256},"ui_locales":{"type":"string","maxLength":256},"redirect_uri":{"type":"string","maxLength":2048},"response_type":{"type":"string","maxLength":32},"scope":{"type":"string","maxLength":2048},"state":{"type":"string","maxLength":1024},"nonce":{"type":"string","maxLength":1024},"code_challenge":{"type":"string","maxLength":128},"code_challenge_method":{"type":"string","maxLength":16},"login_hint":{"type":"string","maxLength":512},"prompt":{"type":"string","maxLength":64},"request_uri":{"type":"string","maxLength":512},"resource":{"type":"string","maxLength":2048},"authorization_details":{"type":"string","maxLength":8192},"dpop_jkt":{"type":"string","minLength":43,"maxLength":43,"pattern":"^[A-Za-z0-9_-]+$"},"max_age":{"type":"integer","format":"int64","minimum":0},"continue_path":{"type":"string","maxLength":128}}},"AuthStepResponse":{"type":"object","description":"The next step for the hosted UI after an auth action. `redirectTo` (when present) is a server-built URL the browser follows — the /authorize continuation or the final client redirect. The MFA/consent fields are reserved for the interactive flows layered on top of this step.","properties":{"redirectTo":{"type":"string","description":"URL to navigate to next (the /authorize continuation or client redirect).","example":"/authorize?client_id=nauthera_2Rk9&response_type=code&..."},"mfaRequired":{"type":"boolean","description":"An MFA challenge is required before completing sign-in."},"mfaMethods":{"type":"array","description":"Available MFA methods, most-preferred first.","items":{"type":"string"}},"consentRequired":{"type":"boolean","description":"User consent is required before issuing tokens."},"setupRequired":{"type":"boolean","description":"The user has no active second factor and must enroll one (forced TOTP enrollment) before sign-in can complete."},"emailVerificationRequired":{"type":"boolean","description":"The organization requires a verified address and this account's is not. No session was established; a verification link was sent if one could be, and POST /v1/auth/verify-email/resend sends another."}}},"RegisterUserRequest":{"type":"object","description":"A hosted-UI local registration. `email` must be a valid address (it also becomes the username); `password` is argon2id-hashed server-side. The preserved OAuth parameters are carried and re-validated exactly as for sign-in.","additionalProperties":false,"required":["email","password"],"properties":{"email":{"type":"string","format":"email","maxLength":320,"example":"alice@example.com"},"password":{"type":"string","minLength":8,"maxLength":4096},"name":{"type":"string","maxLength":256},"captcha":{"type":"string","maxLength":4096,"description":"The captcha solution, required when the deployment asks for one: for `altcha`, the base64 payload the widget produces from a challenge of `GET /v1/auth/captcha`; for a hosted provider, its widget's token."},"client_id":{"type":"string","maxLength":256},"ui_locales":{"type":"string","maxLength":256},"redirect_uri":{"type":"string","maxLength":2048},"response_type":{"type":"string","maxLength":32},"scope":{"type":"string","maxLength":2048},"state":{"type":"string","maxLength":1024},"nonce":{"type":"string","maxLength":1024},"code_challenge":{"type":"string","maxLength":128},"code_challenge_method":{"type":"string","maxLength":16},"login_hint":{"type":"string","maxLength":512},"prompt":{"type":"string","maxLength":64},"request_uri":{"type":"string","maxLength":512},"resource":{"type":"string","maxLength":2048},"authorization_details":{"type":"string","maxLength":8192},"dpop_jkt":{"type":"string","minLength":43,"maxLength":43,"pattern":"^[A-Za-z0-9_-]+$"},"max_age":{"type":"integer","format":"int64","minimum":0},"continue_path":{"type":"string","maxLength":128}}},"CaptchaChallenge":{"type":"object","description":"The captcha this deployment asks for. `none` asks for nothing. `altcha` is the built-in proof-of-work: `parameters` and `signature` are an ALTCHA v2 challenge, signed by the server and valid until `parameters.expiresAt`, with the keys spelled as the widget reads them. `turnstile`, `hcaptcha` and `recaptcha` (v2) are hosted providers: the page shows the provider's widget with `site_key`, and the token it yields is the solution.","additionalProperties":false,"required":["provider"],"properties":{"provider":{"type":"string","enum":["none","altcha","turnstile","hcaptcha","recaptcha"]},"site_key":{"type":"string","description":"The hosted provider's public site key."},"parameters":{"$ref":"#/components/schemas/CaptchaParameters"},"signature":{"type":"string","description":"Hex HMAC-SHA256 over `parameters`, as canonical JSON."}}},"CaptchaParameters":{"type":"object","description":"An ALTCHA v2 challenge: find the counter whose key, derived from `nonce` and the counter with `algorithm` over `salt` at `cost`, starts with `keyPrefix`.","additionalProperties":false,"required":["algorithm","cost","expiresAt","keyLength","keyPrefix","nonce","salt"],"properties":{"algorithm":{"type":"string","example":"PBKDF2/SHA-256"},"cost":{"type":"integer","format":"int64","description":"Iterations of the derivation per attempt."},"expiresAt":{"type":"integer","format":"int64","description":"When the challenge lapses, in seconds since the epoch."},"keyLength":{"type":"integer","format":"int64"},"keyPrefix":{"type":"string"},"nonce":{"type":"string"},"salt":{"type":"string"}}},"PasswordResetRequest":{"type":"object","description":"A request for a password-reset link to the account holding `email`.","additionalProperties":false,"required":["email"],"properties":{"email":{"type":"string","format":"email","maxLength":320,"example":"alice@example.com"},"captcha":{"type":"string","maxLength":4096,"description":"The captcha solution, required when the deployment asks for one."}}},"PasswordResetConfirmRequest":{"type":"object","description":"The token from a password-reset link's fragment, and the new password, held to the same limits as registration's.","additionalProperties":false,"required":["token","password"],"properties":{"token":{"type":"string","minLength":1,"maxLength":128},"password":{"type":"string","minLength":8,"maxLength":4096}}},"InvitationAcceptRequest":{"type":"object","description":"The token from an invitation link's fragment, and the password the invitee chose, held to the same limits as registration's.","additionalProperties":false,"required":["token","password"],"properties":{"token":{"type":"string","minLength":1,"maxLength":128},"password":{"type":"string","minLength":8,"maxLength":4096}}},"EmailVerificationRequest":{"type":"object","description":"A request for a link that verifies the address of the account holding `email`.","additionalProperties":false,"required":["email"],"properties":{"email":{"type":"string","format":"email","maxLength":320,"example":"alice@example.com"},"captcha":{"type":"string","maxLength":4096,"description":"The captcha solution, required when the deployment asks for one."}}},"EmailVerificationConfirmRequest":{"type":"object","description":"The token from a verification link's fragment.","additionalProperties":false,"required":["token"],"properties":{"token":{"type":"string","minLength":1,"maxLength":128}}},"LogoutConfirmRequest":{"type":"object","description":"The user's answer to a parked RP-initiated logout, plus the opaque handle naming it. The handle carries no meaning — the validated post_logout_redirect_uri, state and client_id are held server-side, so the confirmation round trip cannot introduce a destination that was never checked.","required":["handle","approve"],"properties":{"handle":{"type":"string","maxLength":128,"description":"The single-use handle from the logout screen's `lr` parameter."},"approve":{"type":"boolean","description":"True to end the session, false to keep it.","example":true}}},"LogoutConfirmResponse":{"type":"object","description":"Where the browser goes next.","required":["loggedOut"],"properties":{"loggedOut":{"type":"boolean","description":"Whether the session was actually revoked.","example":true},"redirectTo":{"type":"string","description":"The validated post_logout_redirect_uri with `state` echoed when the user confirmed, or the provider's own logged-out page. Never a client-supplied URL that was not registered."}}},"ConsentPromptRequest":{"type":"object","description":"The authorization request the screen is about to describe, carried so the server can re-validate it against the client registry and answer for THAT request.\nIt is deliberately NOT the sign-in body: by this point the user is already authenticated by cookie, and a schema that requires credentials would make an already-signed-in user's request fail validation. The parameters are exactly the ones preserved across the login round trip.","properties":{"client_id":{"type":"string","maxLength":256},"redirect_uri":{"type":"string","maxLength":2048},"response_type":{"type":"string","maxLength":32},"scope":{"type":"string","maxLength":2048},"state":{"type":"string","maxLength":1024},"nonce":{"type":"string","maxLength":1024},"code_challenge":{"type":"string","maxLength":128},"code_challenge_method":{"type":"string","maxLength":16},"ui_locales":{"type":"string","maxLength":256},"request_uri":{"type":"string","maxLength":512},"resource":{"type":"string","maxLength":2048},"authorization_details":{"type":"string","maxLength":8192},"dpop_jkt":{"type":"string","minLength":43,"maxLength":43,"pattern":"^[A-Za-z0-9_-]+$"},"max_age":{"type":"integer","format":"int64","minimum":0},"prompt":{"type":"string","maxLength":64,"description":"OIDC Core §3.1.2.1 `prompt`. Carried so the screen describes the request the way the resumed /authorize will evaluate it: with prompt=consent the full scope set is re-confirmed, without it only the delta an existing grant does not already cover."}}},"ConsentPromptResponse":{"type":"object","description":"What a client is asking the signed-in user to agree to, resolved SERVER-SIDE from the validated authorization request. Nothing here is taken from the query string: a consent screen that renders an attacker-supplied application name or scope list is a ready-made phishing page (OIDC Core §16.18 on the risks of the authorization UI).","required":["client_id","scopes"],"properties":{"client_id":{"type":"string","example":"tv-app"},"client_name":{"type":"string","description":"The registered display name, or the client_id when none is set.","example":"Living Room TV"},"client_logo_uri":{"type":"string","format":"uri"},"account_email":{"type":"string","description":"The signed-in account, so the user can tell WHICH account they are granting from.","example":"alice@example.com"},"scopes":{"type":"array","description":"The scopes not yet covered by an existing grant. Only the delta is listed: re-asking for what the user already agreed to trains people to click through, which is the opposite of what the screen is for.","items":{"type":"string"},"example":["openid","profile"]},"resource":{"type":"string","format":"uri","description":"The RFC 8707 resource this agreement would cover, when the request named one."},"authorization_details_changed":{"type":"boolean","description":"The request carries RFC 9396 authorization details the existing grant does not cover. Flagged separately because a structured authorization is not a scope and cannot be listed as one."}}},"ConsentDecisionRequest":{"type":"object","description":"The user's approve/deny decision, together with the authorization request it applies to. The request parameters are carried rather than parked server-side because the decision must be re-validated against the client registry anyway — the SPA is never trusted, so nothing is gained by trusting a handle it holds either.","required":["approve"],"properties":{"approve":{"type":"boolean","description":"True to record the grant and resume, false to refuse.","example":true},"client_id":{"type":"string","maxLength":256},"redirect_uri":{"type":"string","maxLength":2048},"response_type":{"type":"string","maxLength":32},"scope":{"type":"string","maxLength":2048},"state":{"type":"string","maxLength":1024},"nonce":{"type":"string","maxLength":1024},"code_challenge":{"type":"string","maxLength":128},"code_challenge_method":{"type":"string","maxLength":16},"ui_locales":{"type":"string","maxLength":256},"request_uri":{"type":"string","maxLength":512},"resource":{"type":"string","maxLength":2048},"authorization_details":{"type":"string","maxLength":8192},"dpop_jkt":{"type":"string","minLength":43,"maxLength":43,"pattern":"^[A-Za-z0-9_-]+$"},"max_age":{"type":"integer","format":"int64","minimum":0}}},"ConsentDecisionResponse":{"type":"object","description":"Where the browser goes next.","required":["approved"],"properties":{"approved":{"type":"boolean","example":true},"redirectTo":{"type":"string","description":"The server's own /authorize continuation on approval, or the client's redirect_uri carrying access_denied on refusal. Never a SPA-supplied URL."}}},"DeviceVerifyRequest":{"type":"object","description":"A user-entered device user code (RFC 8628 §3.3).","required":["user_code"],"properties":{"user_code":{"type":"string","description":"The code shown on the device. Case and separators are normalized server-side, so \"wdjb-mjht\", \"WDJB MJHT\" and \"WDJBMJHT\" are the same code — §6.1 recommends exactly that leniency, since the user is transcribing from a screen.","minLength":1,"maxLength":32,"example":"WDJB-MJHT"}}},"DeviceVerifyResponse":{"type":"object","description":"What the device is asking for, so the user can decide.","required":["client_id","user_code"],"properties":{"client_id":{"type":"string","example":"tv-app"},"client_name":{"type":"string","example":"Living Room TV"},"user_code":{"type":"string","example":"WDJB-MJHT"},"scope":{"type":"string","example":"openid profile"},"account_email":{"type":"string","example":"alice@example.com"},"expires_in":{"type":"integer","format":"int64","description":"Seconds until the request expires.","example":900}}},"DeviceDecisionRequest":{"type":"object","description":"The user's approve/deny decision for a device authorization.","required":["user_code","approve"],"properties":{"user_code":{"type":"string","minLength":1,"maxLength":32,"example":"WDJB-MJHT"},"approve":{"type":"boolean","description":"True to grant the device access, false to refuse it.","example":true}}},"DeviceDecisionResponse":{"type":"object","description":"Confirmation that the decision was recorded.","required":["approved"],"properties":{"approved":{"type":"boolean","example":true}}},"TotpEnrollmentResponse":{"type":"object","description":"The show-once TOTP enrollment payload: the otpauth:// URI for a QR code and the Base32 secret for manual entry, plus the compute parameters.","required":["otpauthUri","secret","issuer","account","algorithm","digits","period"],"properties":{"otpauthUri":{"type":"string","description":"The otpauth://totp/... provisioning URI (render as a QR code).","example":"otpauth://totp/Nauthera:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Nauthera"},"secret":{"type":"string","description":"The Base32 (unpadded) secret, for manual entry.","example":"JBSWY3DPEHPK3PXP"},"issuer":{"type":"string","example":"Nauthera"},"account":{"type":"string","example":"alice@example.com"},"algorithm":{"type":"string","example":"SHA1"},"digits":{"type":"integer","example":6},"period":{"type":"integer","example":30}}},"MfaCodeRequest":{"type":"object","description":"A TOTP code for activation, challenge verification, regeneration, or disable.","additionalProperties":false,"required":["code"],"properties":{"code":{"type":"string","description":"The one-time code from the authenticator app.","minLength":6,"maxLength":16,"example":"123456"}}},"RecoveryCodesResponse":{"type":"object","description":"A freshly issued set of single-use recovery codes, shown exactly once.","required":["codes"],"properties":{"codes":{"type":"array","description":"The plaintext recovery codes (store securely; not retrievable again).","items":{"type":"string"},"example":["ABCDE-FGHJK","MNPQR-STUVW"]}}},"RecoveryCodeRequest":{"type":"object","description":"A single-use recovery code redeemed in place of a TOTP code.","additionalProperties":false,"required":["code"],"properties":{"code":{"type":"string","description":"A recovery code (normalization strips dashes/spaces server-side).","minLength":1,"maxLength":64,"example":"ABCDE-FGHJK"}}},"MfaStatusResponse":{"type":"object","description":"The user's MFA enrollment status for settings screens.","required":["enrolled"],"properties":{"enrolled":{"type":"boolean","description":"Whether the user has an active second factor."},"methods":{"type":"array","description":"Enrolled method kinds (e.g. \"totp\").","items":{"type":"string"}},"recoveryCodesRemaining":{"type":"integer","description":"How many unused recovery codes remain.","example":8}}},"OrganizationList":{"type":"object","description":"One page of organizations under cursor (keyset) pagination: `items` holds\nthe page, ordered by the request's `sort`, and `page` says where it sits\nin the listing. Follow `page.next_cursor` while `page.has_more` is true.\n","required":["items","page"],"properties":{"items":{"type":"array","description":"The organizations on this page.","items":{"$ref":"#/components/schemas/Organization"}},"page":{"$ref":"#/components/schemas/CursorPage"}}},"Organization":{"type":"object","description":"An organization (org) as seen by the admin API. `issuer` is the org's\ncanonical OIDC issuer. `admin_m2m_secret` is present ONLY in the 201 create\nresponse and carries the one-time admin M2M client secret; it is never\nreturned again.\n","required":["id","slug","display_name","is_main","issuer","fips_mode","fapi_mode","email_verification_required","created_at","updated_at"],"properties":{"id":{"type":"string","description":"The organization id (UUID)."},"slug":{"type":"string","description":"The org slug (addressed at /orgs/{slug})."},"display_name":{"type":"string"},"is_main":{"type":"boolean","description":"Whether this is the main org (whose admins manage all orgs)."},"issuer":{"type":"string","description":"The org's canonical OIDC issuer URL."},"fips_mode":{"type":"boolean","description":"Whether the org is restricted to the FIPS 140-3 approved algorithm set. Fixed at creation."},"fapi_mode":{"type":"boolean","description":"Whether the org holds its clients to the FAPI 2.0 Security Profile. The stored flag: a deployment-wide pushed-authorization requirement applies whatever it says."},"email_verification_required":{"type":"boolean","description":"Whether an account must have a verified address before sign-in or registration gives it a session. Until it verifies, a correct password gets the verification step and a link instead."},"email_verification_blocked":{"type":"boolean","description":"Present, and true, when verification is required but the organization has no email relay: no link can be sent, so every unverified account is kept out until an administrator verifies it or a relay is configured."},"branding":{"additionalProperties":true,"description":"The organization's default branding document, the shape a client's branding carries: every sign-in page under the organization wears it, with a client's own branding laid over it field by field. Absent when it has none."},"admin_m2m_secret":{"type":"string","description":"One-time admin M2M client secret; present only in the create response."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CursorPage":{"type":"object","description":"Where one page sits in a cursor (keyset) paginated list, returned beside\nthat page's `items`.\n\nWalk a list by following `next_cursor` for as long as `has_more` is true,\npassing the token back verbatim as the `cursor` query parameter. Keep the\nlisting and its filters the same for the whole walk; `limit` MAY change\nbetween pages (a cursor marks a position, not a page number), and `sort`\ntravels in the cursor so it need not be resent. `has_more` is\nauthoritative: it is derived from whether a further row actually exists,\nso a page that happens to be full is not mistaken for a page that has a\nsuccessor, and a walk never costs a trailing empty request. `prev_cursor`\nsteps back the same way.\n\nA cursor is opaque — server-defined, bound to this listing and sort\norder, and not parseable by clients. It stays valid across page-size\nchanges but not across a change of sort order or filters; a cursor that\ndoes not belong to the request is rejected with a 400 rather than\nsilently paging through the wrong set.\n\nKeyset paging is stable under concurrent writes, with one qualification.\nA row whose SORT KEY does not change is returned exactly once for the\nwhole walk. Rows created or deleted mid-walk may or may not appear,\ndepending on which side of the cursor they fall — and so may a row whose\nsort key is UPDATED mid-walk, since that moves it across the boundary: it\ncan be seen twice or missed. Sorting by a field that does not change (the\ndefault id ordering, or `created_at`) avoids this entirely.\n","required":["limit","sort","has_more","has_prev"],"properties":{"limit":{"type":"integer","description":"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":{"type":"string","description":"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":{"type":"boolean","description":"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":{"type":"boolean","description":"Whether a page precedes this one and `prev_cursor` can reach it. False on the first page of a walk.\n\nBoth 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":{"type":"string","maxLength":512,"description":"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."},"prev_cursor":{"type":"string","maxLength":512,"description":"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."}}},"CreateOrganizationRequest":{"type":"object","description":"A new organization (org). `slug` must be a bare path segment and cannot be the reserved main slug (\"default\").","additionalProperties":false,"required":["slug"],"properties":{"slug":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"The org slug (lowercase alphanumeric and hyphens)."},"display_name":{"type":"string","maxLength":200,"description":"Defaults to the slug when omitted."},"fips_mode":{"type":"boolean","description":"Restrict the org to the FIPS 140-3 approved algorithm set. Refused with 409 by a server not running the validated module. Defaults to false and is fixed at creation."},"fapi_mode":{"type":"boolean","description":"Hold the org's clients to the FAPI 2.0 Security Profile. It only tightens the deployment-wide floor. Defaults to false."},"email_verification_required":{"type":"boolean","description":"Require a verified address before sign-in completes. Defaults to false."},"branding":{"additionalProperties":true,"description":"The organization's default branding document. Its browser-facing URLs are held to the schemes a client's branding is."}}},"UpdateOrganizationRequest":{"type":"object","description":"A partial update of an organization. Every field is optional; omitted fields\nare left unchanged. The slug and FIPS mode are fixed at creation.\n","additionalProperties":false,"properties":{"display_name":{"type":"string","maxLength":200},"fapi_mode":{"type":"boolean","description":"Hold the org's clients to the FAPI 2.0 Security Profile. It only tightens the deployment-wide floor."},"branding":{"additionalProperties":true,"description":"Replaces the organization's default branding document; an empty object clears it."},"email_verification_required":{"type":"boolean","description":"Require a verified address before sign-in completes."}}},"DashboardTheme":{"type":"object","description":"An organization's dashboard theme: the reduced subset of its default\nbranding, with the branding document of its reserved admin console\nclient laid over it, that the admin dashboard wears, and the same JSON\nthe dashboard's shell carries before it boots. Nothing that could\nrestyle or hide the controls an administrator acts on is served: no\ncomponent overrides, custom properties other than colors and radii,\nlinks, layout or hero\ncontent, a logo only inline or from the dashboard's own origin, and\nfonts only from the allowlisted font hosts. An organization with\nneither branding is served the neutral theme, `mode` alone.\n","required":["mode"],"properties":{"display_name":{"type":"string","maxLength":100,"description":"The name the branding shows for the organization."},"mode":{"type":"string","enum":["system","light","dark"],"description":"The color mode the theme starts in."},"logo":{"type":"string","maxLength":65536,"description":"The logo: a base64 image data URI of at most 64 KiB, or a path on the dashboard's own origin."},"fonts":{"type":"array","maxItems":4,"description":"Font stylesheet URLs, on the allowlisted font hosts only.","items":{"type":"string"}},"color":{"additionalProperties":true,"description":"The design system's color tokens."},"typography":{"additionalProperties":true,"description":"The design system's typography tokens."},"radius":{"additionalProperties":true,"description":"The design system's radius tokens."},"tokens":{"additionalProperties":true,"description":"The branding's `--color-*` and `--radius-*` custom properties, each a value or a light and dark pair. The dashboard applies the ones it allows and ignores the rest."}}},"OrganizationMail":{"type":"object","description":"The SMTP relay an organization's email is sent through, for display. It\nis configured in the deployment (Helm `mailOrganizations`, or the\ndeployment-wide `config.mail`), not through the API. Credentials are\nnever read or returned, only whether they are configured.\n","required":["source","credentials_configured"],"properties":{"source":{"type":"string","enum":["organization","deployment","none"],"description":"`organization`: the organization's own relay. `deployment`: the deployment's, the organization having none. `none`: neither, so the organization cannot send email."},"host":{"type":"string","maxLength":253,"description":"The relay's host name or IP address."},"port":{"type":"integer","minimum":1,"maximum":65535},"tls_mode":{"type":"string","enum":["starttls","implicit","none"],"description":"How the connection is secured. `none` is a relay the operator explicitly configured without TLS: messages, and credentials if it authenticates, cross the network unencrypted."},"credentials_configured":{"type":"boolean","description":"Whether the relay authenticates with a username and password."},"from_address":{"type":"string","maxLength":254,"description":"The sender address messages carry."},"from_name":{"type":"string","maxLength":256},"reply_to":{"type":"string","maxLength":254}}},"OrganizationMailTestRequest":{"type":"object","additionalProperties":false,"properties":{"locale":{"type":"string","maxLength":35,"description":"The language to write the test message in, a BCP 47 tag. One the server does not ship falls back to the organization's, then English."}}},"OrganizationMailTestResult":{"type":"object","description":"What the relay answered to a test message.","required":["delivered","recipient"],"properties":{"delivered":{"type":"boolean","description":"The relay accepted the message."},"recipient":{"type":"string","maxLength":254,"description":"The address the message went to, the caller's own."},"stage":{"type":"string","maxLength":32,"description":"For a refusal, the step it happened at: dial, tls, greeting, hello, auth, mail, rcpt, data."},"reply_code":{"type":"integer","description":"For a refusal the relay answered, its SMTP reply code."},"permanent":{"type":"boolean","description":"For a refusal, whether trying again can help."},"message":{"type":"string","maxLength":500,"description":"For a refusal, what went wrong. Never a credential."}}},"OrganizationSnapshot":{"type":"object","description":"How many users, sessions and OIDC clients an organization holds at one\ninstant, by state, and how recently its users last signed in.\nAggregates only: no user, session or client is named. The window is\nthe one asked for widened to whole minutes and echoed; it bounds only\nthe users counted as new. Every other figure is as of `computed_at`.\n\nAn answer is cached per organization and window for at least the\ndashboard's shortest refresh interval, so `computed_at` may be that\nmuch older than the request.\n","required":["from","to","computed_at","users","sessions","clients","last_sign_in"],"additionalProperties":false,"properties":{"from":{"type":"string","format":"date-time","description":"The window's inclusive start."},"to":{"type":"string","format":"date-time","description":"The window's exclusive end."},"computed_at":{"type":"string","format":"date-time","description":"The instant the locks, suspensions, sessions and recency were judged at."},"users":{"$ref":"#/components/schemas/SnapshotUsers"},"sessions":{"$ref":"#/components/schemas/SnapshotSessions"},"clients":{"$ref":"#/components/schemas/SnapshotClients"},"last_sign_in":{"$ref":"#/components/schemas/SnapshotLastSignIn"}}},"SnapshotUsers":{"type":"object","description":"The organization's users by state, as of `computed_at`.","required":["total","new","locked","suspended","blocked","with_second_factor","never_signed_in","dormant"],"additionalProperties":false,"properties":{"total":{"type":"integer","format":"int64","minimum":0},"new":{"type":"integer","format":"int64","minimum":0,"description":"Users created in the window."},"locked":{"type":"integer","format":"int64","minimum":0,"description":"Users the automatic lock after repeated wrong passwords refuses now."},"suspended":{"type":"integer","format":"int64","minimum":0,"description":"Users whose status in force is `suspended`; a suspension whose end has passed counts as active."},"blocked":{"type":"integer","format":"int64","minimum":0,"description":"Users whose status in force is `blocked`."},"with_second_factor":{"type":"integer","format":"int64","minimum":0,"description":"Users holding an active second factor."},"never_signed_in":{"type":"integer","format":"int64","minimum":0,"description":"Users who have never signed in."},"dormant":{"type":"integer","format":"int64","minimum":0,"description":"Users who have signed in, but not within the last edge of `last_sign_in`: `last_sign_in.older`."}}},"SnapshotSessions":{"type":"object","description":"The organization's sessions, as of `computed_at`.","required":["live"],"additionalProperties":false,"properties":{"live":{"type":"integer","format":"int64","minimum":0,"description":"Sessions that would still sign their user in: not yet at their absolute expiry and used within the idle timeout. The sessions the sessions listing shows."}}},"SnapshotClients":{"type":"object","description":"The organization's OIDC clients by how they authenticate and how they came to be registered. A client registered with a method or source this document does not name counts in `total` alone.","required":["total","by_type","by_auth_method","by_source"],"additionalProperties":false,"properties":{"total":{"type":"integer","format":"int64","minimum":0},"by_type":{"$ref":"#/components/schemas/SnapshotClientTypes"},"by_auth_method":{"$ref":"#/components/schemas/SnapshotClientAuthMethods"},"by_source":{"$ref":"#/components/schemas/SnapshotClientSources"}}},"SnapshotClientTypes":{"type":"object","description":"Clients per `ClientType`.","required":["public","confidential"],"additionalProperties":false,"properties":{"public":{"type":"integer","format":"int64","minimum":0},"confidential":{"type":"integer","format":"int64","minimum":0}}},"SnapshotClientAuthMethods":{"type":"object","description":"Clients per `ClientAuthMethod`.","required":["none","client_secret_basic","client_secret_post","private_key_jwt"],"additionalProperties":false,"properties":{"none":{"type":"integer","format":"int64","minimum":0},"client_secret_basic":{"type":"integer","format":"int64","minimum":0},"client_secret_post":{"type":"integer","format":"int64","minimum":0},"private_key_jwt":{"type":"integer","format":"int64","minimum":0}}},"SnapshotClientSources":{"type":"object","description":"Clients per `ClientSource`.","required":["operator","dynamic","provisioner"],"additionalProperties":false,"properties":{"operator":{"type":"integer","format":"int64","minimum":0},"dynamic":{"type":"integer","format":"int64","minimum":0},"provisioner":{"type":"integer","format":"int64","minimum":0}}},"SnapshotLastSignIn":{"type":"object","description":"How recently the organization's users last signed in, measured back from `computed_at`: one bucket per edge, each holding the users whose last sign-in is within that edge and not within the edge before it, then the users whose last sign-in is older than the last edge, and the users who never signed in. The buckets, `older` and `never` sum to `users.total`.","required":["buckets","older","never"],"additionalProperties":false,"properties":{"buckets":{"type":"array","maxItems":16,"description":"In ascending order of their edge.","items":{"$ref":"#/components/schemas/SnapshotRecencyBucket"}},"older":{"type":"integer","format":"int64","minimum":0},"never":{"type":"integer","format":"int64","minimum":0}}},"SnapshotRecencyBucket":{"type":"object","required":["within_days","count"],"additionalProperties":false,"properties":{"within_days":{"type":"integer","format":"int64","minimum":1,"description":"The bucket's edge, in days before `computed_at`."},"count":{"type":"integer","format":"int64","minimum":0}}},"ClientType":{"type":"string","description":"Whether a client authenticates at the token endpoint: a public client holds no credential, a confidential one holds a secret or a key.","enum":["public","confidential"]},"ClientAuthMethod":{"type":"string","description":"How a client authenticates at the token endpoint (RFC 7591 §2).","enum":["none","client_secret_basic","client_secret_post","private_key_jwt"]},"ClientGrantType":{"type":"string","description":"A grant type a client may use at the token endpoint (RFC 7591 §2).","enum":["authorization_code","refresh_token","client_credentials","urn:ietf:params:oauth:grant-type:device_code"]},"ClientSource":{"type":"string","description":"How a client came to be registered: by an operator over the control plane, by dynamic registration (RFC 7591), or by the server itself, as an organization's admin console and machine client.","enum":["operator","dynamic","provisioner"]},"AdminClientList":{"type":"object","description":"One page of an organization's clients under cursor (keyset)\npagination: `items` holds the page, ordered by the request's `sort`,\nand `page` says where it sits in the listing. Follow `page.next_cursor`\nwhile `page.has_more` is true.\n","required":["items","page"],"properties":{"items":{"type":"array","description":"The clients on this page.","items":{"$ref":"#/components/schemas/AdminClient"}},"page":{"$ref":"#/components/schemas/CursorPage"}}},"AdminClient":{"type":"object","description":"An OIDC client as the admin API lists it: who it is, how it\nauthenticates and what it may do. No secret, secret hash or\nregistration access token is ever included.\n","required":["id","client_id","display_name","display_name_markers","type","token_endpoint_auth_method","grant_types","source","created_at","updated_at"],"properties":{"id":{"type":"string","description":"The registration id (UUID): the server's id for this registration, stable across updates and distinct from `client_id`."},"client_id":{"type":"string","description":"The OAuth client identifier relying parties present."},"display_name":{"type":"string","description":"The name the client is shown under, as registered; empty when it registered none."},"display_name_markers":{"type":"array","description":"What about the display name is there to be misread, in a fixed order; empty for an ordinary name.","items":{"$ref":"#/components/schemas/ClientNameMarker"}},"type":{"$ref":"#/components/schemas/ClientType"},"token_endpoint_auth_method":{"$ref":"#/components/schemas/ClientAuthMethod"},"grant_types":{"type":"array","description":"The grant types the client is registered for.","items":{"$ref":"#/components/schemas/ClientGrantType"}},"source":{"$ref":"#/components/schemas/ClientSource"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ClientNameMarker":{"type":"string","description":"What about a client's display name is there to be misread: a direction override, isolate or mark (`bidi`), a control character (`control`), invisible characters (`invisible`), surrounding or repeated whitespace (`padded`), a name longer than the sign-in pages show (`overlong`), or markup (`markup`).","enum":["bidi","control","invisible","padded","overlong","markup"]},"AdminClientDetail":{"type":"object","description":"One OIDC client's registered configuration as the admin API shows it:\neverything the list carries, plus the registered metadata an\nadministrator needs to see why a relying party fails. It never carries\na secret, a secret hash or a registration access token — only whether\nthe client holds a secret.\n","required":["id","client_id","display_name","display_name_markers","type","token_endpoint_auth_method","grant_types","source","created_at","updated_at","issuer","redirect_uris","post_logout_redirect_uris","response_types","scopes","pkce_policy","pkce_required","allow_refresh","require_signed_request_object","authorization_details_types","has_secret"],"properties":{"id":{"type":"string","description":"The registration id (UUID)."},"client_id":{"type":"string","description":"The OAuth client identifier relying parties present."},"display_name":{"type":"string","description":"The name the client is shown under; empty when it registered none."},"display_name_markers":{"type":"array","description":"What about the display name is there to be misread, in a fixed order; empty for an ordinary name.","items":{"$ref":"#/components/schemas/ClientNameMarker"}},"type":{"$ref":"#/components/schemas/ClientType"},"token_endpoint_auth_method":{"$ref":"#/components/schemas/ClientAuthMethod"},"grant_types":{"type":"array","description":"The grant types the client is registered for.","items":{"$ref":"#/components/schemas/ClientGrantType"}},"source":{"$ref":"#/components/schemas/ClientSource"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"issuer":{"type":"string","description":"The issuer the client is registered under: the `iss` of the tokens it receives, and the base of its discovery document (`{issuer}/.well-known/openid-configuration`)."},"redirect_uris":{"type":"array","description":"The registered redirection URIs, exactly as registered.","items":{"type":"string"}},"post_logout_redirect_uris":{"type":"array","description":"The registered post-logout redirection URIs.","items":{"type":"string"}},"response_types":{"type":"array","description":"The registered response types.","items":{"type":"string"}},"scopes":{"type":"array","description":"The scopes the client may request.","items":{"type":"string"}},"pkce_policy":{"$ref":"#/components/schemas/ClientPKCEPolicy"},"pkce_required":{"type":"boolean","description":"Whether the client must present a PKCE S256 challenge, after the rule that a public client always must."},"allow_refresh":{"type":"boolean","description":"Whether the client may be issued refresh tokens."},"access_token_ttl_seconds":{"type":"integer","format":"int64","description":"The registered access-token lifetime in seconds; absent when the client takes the server's default."},"refresh_token_ttl_seconds":{"type":"integer","format":"int64","description":"The registered refresh-token lifetime in seconds; absent when the client takes the server's default."},"id_token_ttl_seconds":{"type":"integer","format":"int64","description":"The registered ID-token lifetime in seconds; absent when the client takes the server's default."},"id_token_signed_response_alg":{"type":"string","description":"The JWS algorithm the client's ID tokens are signed with; absent when it takes the server's default."},"token_endpoint_auth_signing_alg":{"type":"string","description":"The JWS algorithm a `private_key_jwt` client signs its assertions with; absent when unrestricted."},"jwks_uri":{"type":"string","description":"Where the server fetches the client's public keys from; absent when it registered none."},"require_signed_request_object":{"type":"boolean","description":"Whether every authorization request must be a signed request object."},"authorization_details_types":{"type":"array","description":"The rich authorization request types (RFC 9396) the client may use.","items":{"type":"string"}},"backchannel_logout_uri":{"type":"string","description":"The client's back-channel logout URI; absent when it registered none."},"backchannel_logout_session_required":{"type":"boolean","description":"Whether back-channel logout tokens must carry a `sid`."},"frontchannel_logout_uri":{"type":"string","description":"The client's front-channel logout URI; absent when it registered none."},"frontchannel_logout_session_required":{"type":"boolean","description":"Whether front-channel logout requests must carry `iss` and `sid`."},"has_secret":{"type":"boolean","description":"Whether the client holds a client secret. The secret itself is never returned: only a one-way hash of it is stored, and the plaintext was shown once, when it was minted."},"labels":{"description":"The client's labels, except the server's reserved ones under `nauthera.dev/`, which carry its organization and machine roles; absent when it has none.","additionalProperties":{"type":"string"}},"contacts":{"type":"array","description":"The registration contacts (RFC 7591 §2). Personal data: present only for a caller holding `users.pii.read`, and only when the client registered some.","items":{"type":"string"}},"client_uri":{"type":"string","description":"The client's home page, as it registered it (RFC 7591 §2)."},"logo_uri":{"type":"string","description":"The client's logo, as it registered it (RFC 7591 §2)."},"policy_uri":{"type":"string","description":"The client's privacy policy, as it registered it (RFC 7591 §2)."},"tos_uri":{"type":"string","description":"The client's terms of service, as it registered it (RFC 7591 §2)."},"software_id":{"type":"string","description":"The self-asserted software id it registered (RFC 7591 §2)."},"software_version":{"type":"string","description":"The self-asserted software version it registered (RFC 7591 §2)."}}},"ClientPKCEPolicy":{"type":"string","description":"Whether the client must present a PKCE (S256) challenge on the authorization code grant (RFC 7636): `required_s256`, `optional` or `disabled`. A public client is held to PKCE whatever its policy says; `pkce_required` reports the effective answer.","enum":["required_s256","optional","disabled"]},"AdminClientUsage":{"type":"object","description":"How many distinct users obtained a token for one OIDC client, over the\nlast day, the last 30 days and all time. Aggregates only: no user is\nnamed.\n","required":["id","client_id","daily_users","monthly_users","all_time_users","as_of"],"properties":{"id":{"type":"string","description":"The client's registration id (UUID)."},"client_id":{"type":"string","description":"The OAuth client identifier relying parties present."},"daily_users":{"type":"integer","format":"int64","minimum":0,"description":"Users whose latest recorded token for the client is at most 24 hours older than `as_of`."},"monthly_users":{"type":"integer","format":"int64","minimum":0,"description":"Users whose latest recorded token for the client is at most 30 days older than `as_of`."},"all_time_users":{"type":"integer","format":"int64","minimum":0,"description":"Users who ever obtained a token for the client since `counted_since`."},"counted_since":{"type":"string","format":"date-time","description":"The earliest first use recorded for the client: the start of what `all_time_users` covers. Absent when no user has been counted."},"as_of":{"type":"string","format":"date-time","description":"The instant the windows end at."}}},"AdminIdentityProviderList":{"type":"object","description":"Every identity provider of the organization, by slug. An organization holds few, so the list is not paged.","required":["items"],"properties":{"items":{"type":"array","description":"The organization's identity providers.","items":{"$ref":"#/components/schemas/AdminIdentityProvider"}}}},"AdminIdentityProvider":{"type":"object","description":"An external OpenID Connect provider the organization's users may sign\nin through, as an operator declared it. Which clients offer it is each\nclient's own setting.\n","required":["slug","provider_issuer","client_id","scopes","trust_email","allowed_email_domains","trust_mfa","mfa_acr_values","redirect_uri","secret_fingerprint","created_at","updated_at"],"properties":{"slug":{"type":"string","description":"The provider's name within the organization, as clients and paths name it.","example":"google"},"display_name":{"type":"string","description":"The name the sign-in pages show. Absent when it has none, and the slug is shown.","example":"Google Workspace"},"provider_issuer":{"type":"string","format":"uri","description":"The upstream issuer whose discovery document the provider is read from.","example":"https://accounts.google.com"},"client_id":{"type":"string","description":"The client id this server signs in with at the provider."},"scopes":{"type":"array","description":"The scopes requested from the provider, `openid` first.","items":{"type":"string"}},"trust_email":{"type":"boolean","description":"Whether an address the provider says it verified may be linked to an existing account holding it."},"allowed_email_domains":{"type":"array","description":"The only domains whose verified addresses may sign in through the provider; empty admits any.","items":{"type":"string"}},"trust_mfa":{"type":"boolean","description":"Whether a sign-in the provider reports as multi-factor, by an amr holding `mfa` or an acr in `mfa_acr_values`, needs no second factor here."},"mfa_acr_values":{"type":"array","description":"The acr values the provider issues for a multi-factor sign-in.","items":{"type":"string"}},"redirect_uri":{"type":"string","format":"uri","description":"The address the provider redirects back to, registered with it."},"secret_fingerprint":{"type":"string","description":"Identifies the sealed client secret in use; it changes exactly when the secret is replaced. Never the secret itself.","example":"sha256:5f2b8c0d4e6a1b3c7d9e0f1a2b3c4d5e"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"UserSearchField":{"type":"string","description":"A user field a search matches on. The display name (the `name` attribute) and the email are personal data, matched only for a caller holding `users.pii.read`.","enum":["username","display_name","email"]},"UserAccountStatus":{"type":"string","description":"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>`.\n**Compatibility:** closed. A client should render a value it does not know as unavailable rather than active.","enum":["active","suspended","blocked"]},"AdminUserList":{"type":"object","description":"One page of users under cursor (keyset) pagination: `items` holds the\npage, ordered by the request's `sort`, and `page` says where it sits in\nthe listing. Follow `page.next_cursor` while `page.has_more` is true;\nthere is no total to count towards.\n","required":["items","page"],"properties":{"items":{"type":"array","description":"The users on this page.","items":{"$ref":"#/components/schemas/AdminUser"}},"page":{"$ref":"#/components/schemas/CursorPage"}}},"AdminUser":{"type":"object","description":"A local user as seen by the admin API. The argon2id password hash is NEVER\nincluded. `attributes` is the free-form string map stored on the identity\n(e.g. the display \"name\"). The nullable timestamps (locked_until,\nlast_login_at) are omitted when unset. Admin access is not a property of\nthe user: it follows the roles the user is granted in an organization\n(e.g. org-user-admin), which are resolved on every admin request.\n","required":["id","username","email","password_set","invitation_pending","failed_login_attempts","status","created_at","updated_at"],"properties":{"id":{"type":"string","description":"The user id (UUID).","example":"3f1a5b2c-8d4e-4f6a-9b0c-1d2e3f4a5b6c"},"username":{"type":"string","example":"alice@example.com"},"email":{"type":"string","example":"alice@example.com"},"email_verified_at":{"type":"string","format":"date-time","description":"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":{"additionalProperties":{"type":"string"},"description":"Free-form string attributes stored on the identity."},"password_set":{"type":"boolean","description":"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."},"invitation_pending":{"type":"boolean","description":"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":{"type":"integer","description":"Consecutive failed password verifications since the last success."},"locked_until":{"type":"string","format":"date-time","description":"Soft-lock deadline (RFC3339); omitted when the account is not locked."},"last_login_at":{"type":"string","format":"date-time","description":"Last successful sign-in (RFC3339); omitted when the user has never signed in."},"status":{"$ref":"#/components/schemas/UserAccountStatus"},"status_reason":{"type":"string","description":"The administrator's note on the current status; omitted when there is none."},"suspended_until":{"type":"string","format":"date-time","description":"When a suspension lifts by itself (RFC3339); omitted when the account is not suspended or the suspension has no end."},"status_changed_at":{"type":"string","format":"date-time","description":"When the status last changed (RFC3339); omitted when it never has."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CreateUserRequest":{"type":"object","description":"An admin-created local user. `email` also becomes the username when username is omitted. Exactly one of `password`, argon2id-hashed server-side, and `invite` set to true is sent.","additionalProperties":false,"required":["email"],"properties":{"email":{"type":"string","format":"email","maxLength":320,"example":"alice@example.com"},"password":{"type":"string","minLength":8,"maxLength":4096,"description":"The user's password. Omitted when `invite` is true."},"invite":{"type":"boolean","description":"Create the user without a password and email `email` a single-use link to choose one, which works for `auth.invitation.validity` and verifies the address when it is followed. Refused 409 `mail_relay_missing` when the organization has no email relay. Defaults to false."},"locale":{"type":"string","maxLength":35,"description":"The language to write the invitation in, a BCP 47 tag. One the server does not ship falls back to the organization's, then English."},"username":{"type":"string","minLength":1,"maxLength":320,"description":"Defaults to the lowercased email when omitted."},"email_verified":{"type":"boolean","description":"Create the user with `email` already verified, for an address the administrator vouches for. Defaults to false."},"attributes":{"additionalProperties":{"type":"string"},"description":"Free-form string attributes (values must be strings)."}}},"UpdateUserRequest":{"type":"object","description":"A partial update of a user. Every field is optional; omitted fields are\nleft unchanged. `attributes`, when present, REPLACES the whole stored map.\nThe password is not settable here (use the password sub-resource).\nChanging `email` leaves the new address unverified unless\n`email_verified` is sent as true.\n","additionalProperties":false,"properties":{"email":{"type":"string","format":"email","maxLength":320},"username":{"type":"string","minLength":1,"maxLength":320},"email_verified":{"type":"boolean","description":"Mark the user's address, after any change above, verified or not."},"attributes":{"additionalProperties":{"type":"string"}}}},"SetPasswordRequest":{"type":"object","description":"A new password for a user, argon2id-hashed server-side.","additionalProperties":false,"required":["password"],"properties":{"password":{"type":"string","minLength":8,"maxLength":4096}}},"UserInvitationRequest":{"type":"object","additionalProperties":false,"properties":{"locale":{"type":"string","maxLength":35,"description":"The language to write the invitation in, a BCP 47 tag. One the server does not ship falls back to the organization's, then English."}}},"AdminUserDeviceList":{"type":"object","description":"Every browser and network a user signed in from within the server's\nretention, most recently seen first. The server keeps a bounded number\nof each, so the list is not paged.\n","required":["items","withheld"],"properties":{"items":{"type":"array","description":"The user's known browsers and networks.","items":{"$ref":"#/components/schemas/AdminUserDevice"}},"withheld":{"type":"boolean","description":"True when every `label` was left out because the caller lacks the `users.pii.read` permission."}}},"AdminUserDevice":{"type":"object","description":"A browser or network a user signed in from within the server's\nretention, as the new-sign-in alert recognizes them. Neither the marker\ncookie nor the user agent is ever returned: a browser is named by the\nbrowser and system its user agent described, and a network by its\nrange.\n","required":["kind","first_seen","last_seen"],"properties":{"kind":{"$ref":"#/components/schemas/AdminUserDeviceKind"},"label":{"type":"string","description":"A browser's name and system (\"Firefox on Linux\") or a network's range (\"203.0.113.0/24\"). Absent when it could not be read, or when it was withheld (see the list's `withheld`).","example":"Firefox on Linux"},"first_seen":{"type":"string","format":"date-time","description":"The first sign-in from it that the server remembers."},"last_seen":{"type":"string","format":"date-time","description":"The latest sign-in from it."}}},"AdminUserDeviceKind":{"type":"string","description":"What a known device is: a browser, recognized by the marker cookie it kept, or a network, the /24 (IPv4) or /48 (IPv6) its address fell in.","enum":["browser","network"]},"AdminUserIdentityList":{"type":"object","description":"Every identity provider account linked to a user. A user holds few, so the list is not paged.","required":["items","withheld"],"properties":{"items":{"type":"array","description":"The user's linked accounts, oldest first.","items":{"$ref":"#/components/schemas/AdminUserIdentity"}},"withheld":{"type":"boolean","description":"True when every `subject` was left out because the caller lacks the `users.pii.read` permission."}}},"AdminUserIdentity":{"type":"object","description":"An account at one of the organization's identity providers that signs\nin as the user, linked the first time it did.\n","required":["provider","issuer","active","linked_at"],"properties":{"provider":{"type":"string","description":"The slug of the provider the link was made through.","example":"google"},"provider_display_name":{"type":"string","description":"The provider's display name. Absent when it has none, or when no provider of the organization holds the slug any more.","example":"Google Workspace"},"issuer":{"type":"string","format":"uri","description":"The upstream issuer that assigned the subject.","example":"https://accounts.google.com"},"subject":{"type":"string","description":"The account's identifier at that issuer (the ID token's `sub`). Absent when withheld (see the list's `withheld`).","example":"110169484474386276334"},"active":{"type":"boolean","description":"True while the link still signs in: the provider exists and still reads its subjects from `issuer`."},"linked_at":{"type":"string","format":"date-time","description":"When the account first signed in as the user."}}},"UserRoleList":{"type":"object","description":"The roles a user holds in the organization the path names.","required":["roles","assignable"],"properties":{"roles":{"type":"array","items":{"$ref":"#/components/schemas/Role"}},"assignable":{"type":"array","description":"The roles the caller may grant to or revoke from this user, in role\norder: none without `roles.assign` or on the caller's own account,\na platform role only in the main organization, and only a role\nwhose permissions and reach the caller's own roles cover.\n","items":{"$ref":"#/components/schemas/Role"}}}},"Role":{"type":"string","description":"A built-in role id, as stored and as the `roles` claim carries it:\n`<scope>-<job>`, the scope being the organization the role is held in\n(`org`) or every organization (`platform`). A platform role counts\nonly when held in the main organization, where it admits its holder\nto every organization. What each grants is the fixed table in\n`internal/rbac`.\n","enum":["org-viewer","org-auditor","org-support","org-user-admin","org-owner","platform-viewer","platform-auditor","platform-owner"]},"AdminEndedSessionList":{"type":"object","description":"One page of a user's ended sessions under cursor (keyset) pagination,\nordered by the request's `sort`. Follow `page.next_cursor` while\n`page.has_more` is true.\n","required":["items","page"],"properties":{"items":{"type":"array","description":"The ended sessions on this page.","items":{"$ref":"#/components/schemas/AdminEndedSession"}},"page":{"$ref":"#/components/schemas/CursorPage"}}},"AdminEndedSession":{"type":"object","description":"A session that has ended, as the session history keeps it: when it\nbegan, was last used and ended, why it ended, how its user\nauthenticated and the clients that took part in it.\n\n`id` is the session's public id, the `sid` its relying parties\nreceived. The address and user agent it was established from, and the\n`device` read from that user agent, are personal data: a caller without\nthe `users.pii.read` permission receives none of them, and `withheld`\nnames the address and user agent.\n","required":["id","created_at","auth_time","last_seen_at","expires_at","ended_at","end_reason","amr","client_ids","withheld"],"properties":{"id":{"type":"string","description":"The public session id (`sid`)."},"created_at":{"type":"string","format":"date-time","description":"When the user signed in and the session began."},"auth_time":{"type":"string","format":"date-time","description":"When the user last authenticated in this session."},"last_seen_at":{"type":"string","format":"date-time","description":"When the session was last used."},"expires_at":{"type":"string","format":"date-time","description":"The absolute lifetime the session had."},"ended_at":{"type":"string","format":"date-time","description":"When the session ended: when it was ended on purpose, or when its idle timeout or absolute lifetime ran out, whenever that was noticed."},"end_reason":{"$ref":"#/components/schemas/SessionEndReason"},"amr":{"type":"array","description":"The authentication methods the user satisfied (RFC 8176).","items":{"type":"string"}},"acr":{"type":"string","description":"The authentication context class satisfied. Absent when none was recorded."},"client_ids":{"type":"array","description":"The OAuth client ids of the clients that took part in the session, sorted. Empty when none did.","items":{"type":"string"}},"ip":{"type":"string","description":"The address the session was established from. Absent when not recorded, or withheld."},"user_agent":{"type":"string","description":"The user agent the session was established from. Absent when not recorded, or withheld."},"device":{"type":"string","description":"The browser and system the user agent names (\"Firefox on Linux\"). Absent when the user agent names neither, or was withheld.","example":"Firefox on Linux"},"withheld":{"type":"array","description":"The personal fields left out because the caller lacks the `users.pii.read` permission; empty when nothing was withheld.","items":{"$ref":"#/components/schemas/AdminSessionWithheldField"}}}},"SessionEndReason":{"type":"string","description":"Why a session ended: the user signed out (`logout`), an administrator ended it (`admin_revoked`), set the user's password (`password_reset`) or changed the account's status (`account_status`), or it went unused for its idle timeout (`expired_idle`) or reached its absolute lifetime (`expired_absolute`).","enum":["logout","admin_revoked","password_reset","account_status","expired_idle","expired_absolute"]},"AdminSessionWithheldField":{"type":"string","description":"A personal field of a session that was left out because the caller lacks the `users.pii.read` permission: the user's display name, the address the session was established from, or its user agent.","enum":["display_name","ip","user_agent"]},"AdminSessionList":{"type":"object","description":"One page of an organization's active sessions under cursor (keyset)\npagination: `items` holds the page, ordered by the request's `sort`,\nand `page` says where it sits in the listing. Follow `page.next_cursor`\nwhile `page.has_more` is true.\n","required":["items","page"],"properties":{"items":{"type":"array","description":"The sessions on this page.","items":{"$ref":"#/components/schemas/AdminSession"}},"page":{"$ref":"#/components/schemas/CursorPage"}}},"AdminSession":{"type":"object","description":"An active sign-in session as the admin API lists it: whose it is, when\nit began and was last used, how its user authenticated, the clients\nthat took part in it, and when it ends.\n\n`id` is the session's PUBLIC identifier — the `sid` its relying\nparties receive in ID and logout tokens, and the `session_id` its\naudit records carry. The credential the browser holds, and anything\nderived from it, is never returned.\n","required":["id","user_id","username","created_at","auth_time","last_seen_at","idle_expires_at","expires_at","amr","client_ids","withheld"],"properties":{"id":{"type":"string","description":"The public session id: the `sid` claim of every ID token issued in this session."},"user_id":{"type":"string","description":"The id of the user the session belongs to."},"username":{"type":"string","description":"The user's username within the organization."},"display_name":{"type":"string","description":"The user's display name. Absent when they have none, or when it was withheld (see `withheld`)."},"created_at":{"type":"string","format":"date-time","description":"When the user signed in and the session began."},"auth_time":{"type":"string","format":"date-time","description":"When the user last authenticated in this session (OIDC `auth_time`)."},"last_seen_at":{"type":"string","format":"date-time","description":"When the session was last used. Refreshed lazily, so it may trail the most recent use by up to a minute."},"idle_expires_at":{"type":"string","format":"date-time","description":"When the session ends if it is not used again: `last_seen_at` plus the idle timeout. The session ends at whichever of this and `expires_at` comes first."},"expires_at":{"type":"string","format":"date-time","description":"When the session ends however it is used: its absolute lifetime, fixed when it began."},"amr":{"type":"array","description":"The authentication methods the user satisfied (OIDC `amr`, RFC 8176), such as `pwd`, `otp` and `mfa`.","items":{"type":"string"}},"acr":{"type":"string","description":"The authentication context class satisfied (OIDC `acr`). Absent when none was recorded."},"client_ids":{"type":"array","description":"The OAuth client ids of the clients that took part in the session — each was issued an authorization code in it — sorted. Empty when none has yet.","items":{"type":"string"}},"ip":{"type":"string","description":"The address the session was established from. Absent when it was not recorded, or when it was withheld (see `withheld`)."},"user_agent":{"type":"string","description":"The user agent the session was established from. Absent when it was not recorded, or when it was withheld (see `withheld`)."},"withheld":{"type":"array","description":"The personal fields left out of this session because the caller lacks the `users.pii.read` permission, in a fixed order; empty when nothing was withheld. A field named here is absent because it was withheld, not because it is unknown.","items":{"$ref":"#/components/schemas/AdminSessionWithheldField"}}}},"AuditCategory":{"type":"string","description":"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.","enum":["authentication","session","mfa","token","user","organization","client","role","admin","audit","system"]},"AuditOutcome":{"type":"string","description":"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.","enum":["success","failure","denied","error"]},"AuditSeverity":{"type":"string","description":"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`, `login_required`, `interaction_required`, `consent_required`, `account_selection_required`) keeps its action's default.","enum":["info","notice","warning","critical"]},"AuditReason":{"type":"string","description":"A stable machine token explaining a non-success outcome. Absent on\nsuccess.\n\nTranslate it rather than displaying it: **the key is the token verbatim\nin the `auditReasons` namespace** — `auditReasons:invalid_credentials`. A\nseparate namespace from actions, because these are flat tokens rather\nthan dotted paths and would otherwise nest unpredictably.\n\nThe OAuth-related values are spelled exactly as the corresponding RFC\n6749 §5.2 / OIDC Core §3.1.2.6 wire error, so the audit record and the\nerror the client actually received are the same string.\n\n**Compatibility:** closed, with the same cost described on `AuditAction` —\nthis vocabulary grows as the server implements more specifications\n(`invalid_target` arrived with RFC 8707, `invalid_dpop_proof` with RFC\n9449).\n","enum":["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"]},"AuditActorType":{"type":"string","description":"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.","enum":["user","admin","client","system","anonymous"]},"AuditTargetType":{"type":"string","description":"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.","enum":["user","organization","client","session","token","factor","role","system","audit_log","consent"]},"AuditEventList":{"type":"object","description":"One page of audit records under cursor (keyset) pagination: `items` holds\nthe page, ordered by the request's `sort`, and `page` says where it sits\nin the listing. Follow `page.next_cursor` while `page.has_more` is true.\n\nThere is deliberately no total. Counting matching rows on an append-only\ntable means scanning every partition in the window to answer a question\nno client acts on differently — the page itself already reports whether\nmore follow.\n","required":["items","page"],"properties":{"items":{"type":"array","description":"The audit records on this page.","items":{"$ref":"#/components/schemas/AuditEvent"}},"page":{"$ref":"#/components/schemas/CursorPage"}}},"AuditEvent":{"type":"object","description":"One audit record.\n\nThe server NEVER returns a rendered sentence. It returns the event's\n`action` plus structured actor/target/metadata, and the client renders\nthe human text by looking up one translation key and interpolating. That\nis what makes the log translatable, and it is why there is no `message`\nfield to reach for: a stored English sentence cannot be translated,\ncannot be filtered on, and becomes an accidental API that changes on\nevery refactor.\n\nTwo fields carry translation keys: `action` (`auditEvents:<action>`) and\n`reason` (`auditReasons:<reason>`). Both are enumerated vocabularies, so\nthis document alone is enough to build complete translation coverage —\nwhich is the reason they are enumerated.\n\nThat completeness is bought with a compatibility cost, stated on\n`AuditAction`: a client generated against one version of this document\nrejects an entire audit page the first time the server emits a value\nadded after that client was built. Regenerate on upgrade. A client that\nwould rather degrade gracefully than fail closed should decode `action`\nand `reason` as plain strings and keep a fallback label — the values are\nstable and append-only, so an unrecognized one is always a new value and\nnever a renamed one.\n","required":["id","occurred_at","recorded_at","action","category","outcome","severity","organization_id","cross_tenant","actor"],"properties":{"id":{"type":"string","description":"The record id (UUIDv7, so it is also creation-ordered)."},"occurred_at":{"type":"string","format":"date-time","description":"When the event happened."},"recorded_at":{"type":"string","format":"date-time","description":"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":{"$ref":"#/components/schemas/AuditAction"},"category":{"$ref":"#/components/schemas/AuditCategory"},"outcome":{"$ref":"#/components/schemas/AuditOutcome"},"severity":{"$ref":"#/components/schemas/AuditSeverity"},"reason":{"$ref":"#/components/schemas/AuditReason"},"organization_id":{"type":"string","description":"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."},"actor_organization_id":{"type":"string","description":"The organization the ACTOR came from."},"cross_tenant":{"type":"boolean","description":"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":{"$ref":"#/components/schemas/AuditActor"},"target":{"$ref":"#/components/schemas/AuditTarget"},"client_ip":{"type":"string","description":"The caller's address, resolved through the trusted-proxy policy."},"client_ip_source":{"$ref":"#/components/schemas/AuditClientIPSource"},"transport":{"$ref":"#/components/schemas/AuditTransport"},"proxy_ip":{"type":"string","description":"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."},"user_agent":{"type":"string","description":"The raw User-Agent, stored unparsed."},"client_kind":{"$ref":"#/components/schemas/AuditClientKind"},"client_agent":{"type":"string","description":"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":{"type":"string","description":"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":{"type":"string","description":"W3C trace id, for correlating with distributed traces. An inbound `traceparent` is honored only from a trusted proxy."},"span_id":{"type":"string"},"operation":{"type":"string","description":"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":{"type":"string","description":"The request method. A method outside the registered set (RFC 9110 §9 and PATCH) is recorded as `_OTHER`, as OpenTelemetry records it."},"session_id":{"type":"string","description":"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":{"type":"string","description":"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":{"type":"string","description":"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":{"description":"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.","additionalProperties":true}}},"AuditAction":{"type":"string","description":"The event type — what was attempted. Append-only: an action is never\nrenamed once shipped, because a rename orphans every stored record and\nevery SIEM rule matching on it.\n\nThe outcome is deliberately NOT part of the name. `auth.sign_in.completed`\nwith `outcome: failure` is a failed sign-in; there is no\n`auth.sign_in.failed`. Encoding it would double this list, the catalog and\nevery translation file.\n\n**Translation key:** the action verbatim in the `auditEvents` namespace —\n`auditEvents:user.password.changed`. Because i18next splits keys on `.`,\na locale file nests naturally by segment. The catalog endpoint publishes\nthe same key as `i18n_key` so a client need not re-derive it.\n\n**Compatibility:** this is a CLOSED enum, and that has a cost worth\nstating plainly. A client generated against one version of this document\nwill reject an entire audit page — not merely one record — the first time\nthe server emits an action added after that client was built. Regenerate\non upgrade, or decode the field as a plain string. It is enumerated\nanyway because the alternative left every consumer unable to know the\ntranslatable vocabulary without calling the catalog endpoint at runtime.\n","enum":["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","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","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","mfa.factor.locked","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","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.created","user.deleted","user.devices.listed","user.identities.listed","user.invitation.sent","user.listed","user.password.changed","user.read","user.updated"]},"AuditActor":{"type":"object","description":"Who caused the event.\n\n`display` is a snapshot taken when the event was recorded, not a live\nlookup. That is deliberate: an audit record's most important reads happen\nafter the fact, by which time the principal it names may have been\ndeleted — and a log that can only render an opaque id once the row is\ngone has failed at its only job.\n","required":["type"],"properties":{"type":{"$ref":"#/components/schemas/AuditActorType"},"id":{"type":"string"},"display":{"type":"string","description":"A human label captured at event time."},"impersonator_id":{"type":"string","description":"The real principal, when this actor was being acted-as. Present only for actions taken inside an impersonated session."},"impersonator_display":{"type":"string"}}},"AuditTarget":{"type":"object","description":"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.","properties":{"type":{"$ref":"#/components/schemas/AuditTargetType"},"id":{"type":"string"},"display":{"type":"string"}}},"AuditClientIPSource":{"type":"string","description":"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.","enum":["socket","forwarded","proxy"]},"AuditTransport":{"type":"string","description":"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.","enum":["tls","cleartext","forwarded_tls","forwarded_cleartext"]},"AuditClientKind":{"type":"string","description":"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.","enum":["browser","ai_agent","crawler","automation","library","unknown"]},"AuditEventTail":{"type":"object","description":"One page of the audit log in the order its records reached the store,\nfrom the position a `cursor` names.\n\n`next_cursor` is always present: pass it back to continue, whether this\npage was full, short or empty. `has_more` says whether further records\nwere already available beyond this page, so a consumer reads again at\nonce while it is true and waits before reading again once it is false.\n","required":["items","next_cursor","has_more"],"properties":{"items":{"type":"array","description":"The audit records on this page, in arrival order.","items":{"$ref":"#/components/schemas/AuditEvent"}},"next_cursor":{"type":"string","description":"Where the next read resumes: after the last record on this page, or where this read began when the page is empty. Opaque, and bound to the organization and filters of the read that issued it."},"has_more":{"type":"boolean","description":"Whether records beyond this page were already available when it was read. False means the tail has caught up; read again later."}}},"AuditCountBucket":{"type":"string","description":"A bucket length counts are grouped by, each allowed up to a window:\n`1m` up to 2 hours, `10m` up to 2 days, `1h` up to 31 days and `1d` up\nto the 90-day maximum. Buckets are UTC, a day's included.\n","enum":["1m","10m","1h","1d"]},"AuditCountGroup":{"type":"string","description":"The column counts are split by. `client` is the record's client id and `actor_type` its actor's type; the rest are the record's own fields of that name. Grouping by `action` additionally requires `audit.read`.","enum":["action","category","outcome","severity","reason","client","actor_type"]},"AuditEventCounts":{"type":"object","description":"How many audit records matched, per bucket of the window and per group:\na dense, zero-filled UTC series. The window is the one asked for widened\nto whole buckets, and is echoed so a chart labels exactly the buckets it\nwas given.\n\nThis is the bounded exception to the audit list's no-total rule: the\nbucket lengths and their windows are a fixed list, the series times the\nbuckets of one answer are capped, and an answer is cached per caller\nview for at least the dashboard's shortest refresh interval.\n","required":["from","to","bucket","series"],"properties":{"from":{"type":"string","format":"date-time","description":"The first bucket's start."},"to":{"type":"string","format":"date-time","description":"The last bucket's end."},"bucket":{"$ref":"#/components/schemas/AuditCountBucket"},"group_by":{"$ref":"#/components/schemas/AuditCountGroup"},"series":{"type":"array","description":"One series per group, ordered by key. Ungrouped counts are a single series with an empty key; grouped counts carry a series only for the groups that matched something.","items":{"$ref":"#/components/schemas/AuditCountSeries"}}}},"AuditCountSeries":{"type":"object","description":"One group's counts, one per bucket of the window, in order.","required":["key","counts"],"properties":{"key":{"type":"string","description":"The grouped column's value. Empty when the counts are not grouped, and for the records that hold no value there, such as the reason of a success."},"counts":{"type":"array","description":"One count per bucket from `from` to `to`, zero where nothing matched.","items":{"type":"integer","format":"int64","minimum":0}}}},"AuditRankSubject":{"type":"string","description":"What a ranking groups the records by: the principal that caused them (`actor`; anonymous records, which name none, are left out), the object they acted on (`target`), the client they are about, resolved or only named as the list's `client_id` filter matches it (`client`), or the address they came from (`client_ip`, personal data, which additionally requires `users.pii.read`).","enum":["actor","target","client","client_ip"]},"AuditEventRanking":{"type":"object","description":"The subjects behind the audit records a filter matches, most records\nfirst and ties by key, at most `limit` of them. The window is echoed.\n\nThe second bounded exception to the audit list's no-total rule, beside\nthe counts: a ranking groups every record in its window in one pass, so\nits window is bounded to 31 days where the counts allow 90, how many\nsubjects it returns is capped at 25, and an answer is cached per caller\nview for at least the dashboard's shortest refresh interval.\n","required":["from","to","by","limit","items"],"additionalProperties":false,"properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"by":{"$ref":"#/components/schemas/AuditRankSubject"},"limit":{"type":"integer","minimum":1,"maximum":25,"description":"How many subjects were asked for; fewer come back when fewer exist."},"items":{"type":"array","maxItems":25,"items":{"$ref":"#/components/schemas/AuditRankedSubject"}}}},"AuditRankedSubject":{"type":"object","description":"One ranked subject and how many of the matching records it is behind.","required":["key","count"],"additionalProperties":false,"properties":{"key":{"type":"string","maxLength":256,"description":"The subject's identifier: the actor or target id, the client id, or the address."},"display":{"type":"string","maxLength":256,"description":"A display snapshot the records carry for the subject (an actor's or target's display name). Absent for a client or an address, where the records carry none, and for a caller without `users.pii.read`, since a display name is personal data."},"count":{"type":"integer","format":"int64","minimum":1}}},"AuditCatalog":{"type":"object","description":"The complete audit event vocabulary this server can emit, plus the closed\nvalue sets a client needs to render and filter it.\n\nEvery vocabulary here is also enumerated in this document, so a client\ncan build its filter controls and its translation coverage statically.\nWhat this endpoint adds is the per-action DETAIL a bare enum cannot carry\n— category, severity, verbosity level, whether configuration can suppress\nit, its declared metadata keys, and its translation key — and the answer\nto which values THIS BUILD actually emits, which may be a subset of what\nthe document declares once a deployment is older or newer than its\nclient.\n\nEach array's element type matches the corresponding field on a record, so\na value that could be served is a value the document declares. A server\nthat could publish an action it cannot emit would be describing something\nother than itself.\n\n`field_values` covers the metadata fields whose values are a closed\nvocabulary rather than data — the refusal cause, what triggered a\nrevocation, a stop or a degraded audit pipeline, a throttle's scope, the\nstep of a logout fanout, of a user_code submission or of a boot that\nfailed, the screen a client name was shown on, a client authentication\nmethod, the algorithm a refused client assertion or request object\ndeclared, what a consent decision did to the standing agreement, how a\nUserInfo read resolved its subject, the client metadata field a\nregistration refusal or an unframeable front-channel logout was about,\nwhat an RFC 7009 revocation request found, why a logout did not honor its\npost-logout destination, what a logout's id_token_hint proved, what an\naccepted authorization request was answered with, what Sec-Fetch-Site\nsaid when a cross-site check refused on it, which profiling endpoint was\nread, which dependency failed, which kind of operation and why, and what\nthe server uses it for, which forwarding headers an untrusted peer sent,\nand where a request carried a credential over a hop a trusted proxy\nreported as cleartext, how the server's database role could change or\nremove audit records, and which writer lane signed a checkpoint of the\naudit chain and which part of its statement a record holds — so a\ndetection rule can be written against the values the server will\nactually use rather than the ones someone happened to see.\n","required":["actions","categories","outcomes","severities","reasons","causes","actor_types","target_types","field_values"],"properties":{"actions":{"type":"array","items":{"$ref":"#/components/schemas/AuditActionSpec"}},"categories":{"type":"array","uniqueItems":true,"items":{"$ref":"#/components/schemas/AuditCategory"}},"outcomes":{"type":"array","uniqueItems":true,"items":{"$ref":"#/components/schemas/AuditOutcome"}},"severities":{"type":"array","uniqueItems":true,"items":{"$ref":"#/components/schemas/AuditSeverity"}},"reasons":{"type":"array","uniqueItems":true,"description":"The failure vocabulary this build can emit. A client translates a record's `reason` rather than displaying it, so it needs the full set to know which keys to provide.","items":{"$ref":"#/components/schemas/AuditReason"}},"causes":{"type":"array","uniqueItems":true,"description":"The refusal causes this build can record in `metadata.cause`.","items":{"$ref":"#/components/schemas/AuditCause"}},"actor_types":{"type":"array","uniqueItems":true,"items":{"$ref":"#/components/schemas/AuditActorType"}},"target_types":{"type":"array","uniqueItems":true,"items":{"$ref":"#/components/schemas/AuditTargetType"}},"field_values":{"type":"array","description":"The closed value set of every metadata field that has one, in the server's field order.","items":{"$ref":"#/components/schemas/AuditFieldValues"}}}},"AuditActionSpec":{"type":"object","description":"One entry of the server's audit event catalog: everything about an action that does not vary per occurrence.","required":["action","category","severity","level","mandatory","platform","fields","i18n_key","summary"],"properties":{"action":{"$ref":"#/components/schemas/AuditAction"},"category":{"$ref":"#/components/schemas/AuditCategory"},"severity":{"$ref":"#/components/schemas/AuditSeverity"},"level":{"$ref":"#/components/schemas/AuditLevel"},"mandatory":{"type":"boolean","description":"Whether configuration is unable to suppress this action. The non-suppressible set is small and deliberate: an operator who could switch off logging of destructive administrative actions would have a privilege-escalation path rather than a feature."},"platform":{"type":"boolean","description":"Whether the action is platform-scoped (never owned by a tenant), such as process startup or audit retention."},"fields":{"type":"array","description":"The metadata keys an occurrence of this action may carry. It is a closed allowlist enforced at write time, which is what makes it impossible for a credential to reach the payload.","items":{"type":"string"}},"i18n_key":{"type":"string","description":"The translation key for this action — the action verbatim in the `auditEvents` namespace. Published here so a client never re-implements the derivation rule."},"summary":{"type":"string","description":"A short English description. It is documentation and a last-resort fallback for a client with no translation yet — never the stored record."}}},"AuditLevel":{"type":"string","description":"The configured verbosity tier at which an action is recorded, least verbose first. Independent of severity: token refresh is low severity but the highest-volume event an identity provider produces, so it sits at the most verbose tier. It is the level of an action's successes: a refusal or a server fault is recorded at `security` whatever its action declares, so the default configuration keeps every refused token request and no issued one. The configuration also accepts `off`, which is not listed here because no catalog entry can declare it — an action recorded only when mandatory is a mandatory flag written confusingly.","enum":["security","write","read","trace"]},"AuditCause":{"type":"string","description":"The RULE that produced a refusal, carried in a record's `metadata.cause`.\n\nA reason is the error the client received, and one error covers many\nrules on purpose: `invalid_grant` answers an unknown code, a PKCE\nmismatch and a refresh token presented by the wrong client alike, because\ntelling the caller which one fired would tell them whether the\ncredential they hold is genuine. The cause is the operator's half of\nthat distinction. Each token names one rule and means the same thing at\nevery endpoint that applies it; the action, `grant_type`, `method` and\n`metadata_field` say where.\n\nThe second factor uses it the same way: one 401 answers a wrong code, a\nreplayed one, a malformed one and a genuine one from a device whose clock\nis off, and refuses a missing, unknown, expired or foreign pending\nsign-in or session alike. The cause is where they differ.\n\nA cause names a rule, never a value: no code, verifier, redirect URI or\nerror description ever appears here. A value outside this vocabulary is\nnever stored — the server drops it and lists `cause` under `_dropped`.\n\n**Compatibility:** closed and append-only, with the cost described on\n`AuditAction`.\n","enum":["parameter_missing","grant_not_allowed","confidential_client_required","scope_not_registered","scope_exceeds_grant","resource_malformed","resource_not_granted","grant_client_mismatch","dpop_binding_mismatch","code_unknown","code_reused","redirect_uri_mismatch","pkce_verifier_missing","pkce_mismatch","pkce_required","pkce_verifier_unexpected","refresh_unknown","refresh_reused","session_ended","subject_revoked","grant_revoked","device_code_unknown","device_code_expired","authorization_denied","polling_too_fast","token_invalid","token_expired","missing_expiry","org_claim_mismatch","basic_header_malformed","multiple_auth_methods","client_id_mismatch","assertion_type_unsupported","client_unknown","client_lookup_failed","auth_method_mismatch","auth_method_unsupported","secret_missing","secret_not_registered","secret_mismatch","assertion_too_large","assertion_malformed","jwks_uri_missing","registered_alg_not_allowed","assertion_alg_not_allowed","assertion_typ_rejected","critical_header_unsupported","jwks_uri_invalid","jwks_fetch_failed","jwks_malformed","jwks_private_key_published","jwks_no_usable_key","assertion_kid_unknown","assertion_signature_invalid","assertion_sub_missing","assertion_sub_mismatch","assertion_iss_missing","assertion_iss_mismatch","assertion_aud_mismatch","assertion_exp_missing","assertion_expired","assertion_lifetime_too_long","assertion_not_yet_valid","assertion_iat_in_future","assertion_jti_missing","assertion_jti_too_large","assertion_is_request_object","assertion_replayed","replay_store_unavailable","request_canceled","panic_recovered","unknown_identifier","reset_link_unknown","verification_link_unknown","already_verified","invitation_link_unknown","password_mismatch","password_hash_malformed","password_not_set","account_locked","identifier_locked","address_throttled","identifier_throttled","verify_capacity","request_abandoned","account_unavailable","cross_site_request","identifier_taken","reserved_identifier","password_already_set","mail_relay_missing","captcha_missing","captcha_invalid","captcha_expired","captcha_replayed","captcha_unverifiable","code_mismatch","code_replayed","code_malformed","code_other_method","clock_skew","no_codes_left","lock_standing","no_factor","already_enrolled","enrollment_not_activated","verifier_saturated","seed_key_unknown","seed_integrity_failed","pending_absent","pending_unknown","pending_foreign_org","pending_expired","pending_not_enrollment","session_absent","session_unknown","session_foreign_org","session_expired","session_account_deleted","session_account_unavailable","issuer_unresolved","client_not_admin","org_claim_missing","grant_family_missing","software_statement_unsupported","blank_entry","value_unsupported","value_not_permitted","scope_reserved","refresh_without_issuing_grant","grant_response_mismatch","signing_alg_method_mismatch","too_many_values","uri_invalid","host_not_global","native_redirect_not_loopback","credential_missing","registration_token_invalid","operator_managed_client","client_secret_mismatch","concurrent_update","insufficient_scope","organization_missing","identity_not_allowed","operation_not_granted","organization_not_granted","namespace_not_granted","reserved_manager","admin_roles_label","invalid_field","immutable_field","main_organization","fips_unavailable","reserved_client_held","rp_timeout","rp_unreachable","rp_tls_failed","rp_transport_failed","rp_rejected","rp_misconfigured","rp_unavailable","logout_uri_invalid","no_delivery_slot","fanout_deadline","notifier_saturated","notifier_closed","egress_proxy_failed","aborted_at_shutdown","backchannel_not_registered","hint_absent","hint_malformed","hint_type_rejected","hint_key_unknown","hint_signature_invalid","hint_issuer_mismatch","hint_expired","hint_not_yet_valid","hint_claims_missing","hint_unverifiable","hint_subject_mismatch","logout_request_unknown","sid_missing","signing_key_unknown","signing_key_integrity_failed","token_malformed","token_typ_rejected","token_kid_unknown","token_alg_mismatch","token_signature_invalid","token_issuer_mismatch","token_issuer_unknown","token_not_yet_valid","dpop_proof_missing","dpop_proof_repeated","dpop_scheme_required","dpop_proof_too_large","dpop_malformed","dpop_typ_rejected","dpop_alg_not_allowed","dpop_jwk_missing","dpop_jwk_private","dpop_jwk_invalid","dpop_key_unsupported","dpop_signature_invalid","dpop_claim_missing","dpop_jti_too_large","dpop_htm_mismatch","dpop_htu_invalid","dpop_htu_mismatch","dpop_iat_too_old","dpop_iat_in_future","dpop_nonce_missing","dpop_nonce_stale","dpop_ath_missing","dpop_ath_mismatch","dpop_proof_replayed","signed_request_required","response_type_unsupported","response_mode_unsupported","pkce_method_unsupported","account_selection_unsupported","authorization_details_malformed","authorization_details_type_not_registered","request_object_malformed","request_object_alg_not_allowed","request_object_kid_unknown","request_object_signature_invalid","request_object_aud_mismatch","request_object_iss_mismatch","request_object_expired","request_object_not_yet_valid","request_uri_in_push","request_uri_not_pushed","request_uri_consumed","max_age_exceeded","consent_not_enabled","consent_not_granted","body_too_large","body_malformed","parameter_repeated","schema_invalid","cors_origin_denied","throttle_failed_open","throttle_failed_closed","cursor_tampered","cursor_scope_mismatch","cursor_sort_mismatch","unknown_organization","main_organization_prefixed","organization_lookup_failed","organization_build_failed","issuer_location_mismatch","sign_in_unknown","sign_in_too_old","second_factor_required","own_account","target_outranks_caller","owner_tier_required","platform_role_outside_main","role_outranks_caller","last_role_administrator","federation_state_invalid","upstream_denied","upstream_unavailable","upstream_token_invalid","upstream_email_missing","identity_provider_unknown","identity_provider_not_allowed","account_not_linked","email_domain_not_allowed"]},"AuditFieldValues":{"type":"object","description":"The closed set of values one metadata field may carry. A value outside it is never stored: the server drops it and lists the key under `_dropped`, exactly as it does an undeclared key. A field that holds a list (`fails_closed`, `fails_open`, `carried_in`, `headers`, `audit_rewrite_routes`) holds only values from the set, and is dropped whole if one is not.","required":["field","values"],"properties":{"field":{"type":"string","description":"The metadata key."},"values":{"type":"array","description":"Every value the field may carry, in the server's declared order.","items":{"type":"string"}}}},"ThreatOverview":{"type":"object","description":"Every signal of the threat catalog counted over one window, each judged\nagainst its threshold and the equal window before, and the shape of the\nwindow's failed sign-ins. The window is the one asked for widened to\nwhole buckets, and is echoed with the bucket, so a sparkline labels\nexactly the buckets it was given.\n\nThe overview counts audit records, so it sees what the deployment's\naudit level records: a refusal is recorded at the refusal level\nwhatever its action's own, and the records the edge coalesces count\nonce per summary. An answer is cached per organization for at least\nthe dashboard's shortest refresh interval.\n","required":["from","to","bucket","baseline_from","signals","sign_in_shape"],"additionalProperties":false,"properties":{"from":{"type":"string","format":"date-time","description":"The first bucket's start."},"to":{"type":"string","format":"date-time","description":"The last bucket's end."},"bucket":{"$ref":"#/components/schemas/AuditCountBucket"},"baseline_from":{"type":"string","format":"date-time","description":"The start of the equal window before, which ends at `from`."},"signals":{"type":"array","maxItems":64,"description":"Every signal of the catalog, in its display order.","items":{"$ref":"#/components/schemas/ThreatSignal"}},"sign_in_shape":{"$ref":"#/components/schemas/SignInShape"}}},"ThreatSignal":{"type":"object","description":"One signal over the window: how many records it counted, how many the equal window before held, the counts per bucket for a sparkline, the status that judges them, and the threshold and references that explain the judgement.","required":["kind","category","severity","count","baseline_count","counts","status","threshold","filter","references"],"additionalProperties":false,"properties":{"kind":{"$ref":"#/components/schemas/ThreatKind"},"category":{"$ref":"#/components/schemas/ThreatCategory"},"severity":{"$ref":"#/components/schemas/AuditSeverity"},"count":{"type":"integer","format":"int64","minimum":0,"description":"The records counted in the window."},"baseline_count":{"type":"integer","format":"int64","minimum":0,"description":"The records counted in the equal window ending where this one starts."},"counts":{"type":"array","description":"One count per bucket from `from` to `to`, zero where nothing matched.","items":{"type":"integer","format":"int64","minimum":0}},"status":{"$ref":"#/components/schemas/ThreatStatus"},"threshold":{"$ref":"#/components/schemas/ThreatThreshold"},"filter":{"$ref":"#/components/schemas/ThreatFilter"},"references":{"type":"array","maxItems":8,"items":{"$ref":"#/components/schemas/ThreatReference"}}}},"ThreatKind":{"type":"string","description":"One signal of the threat overview: a closed set of audit actions and\noutcomes counted over the window. Translated under\n`threats.signal.<value>`.\n\n**Compatibility:** closed. A client should render a kind it does not\nknow by its count and status alone.\n","enum":["sign_in_failures","account_locks","sign_in_throttles","recovery_refusals","registration_refusals","second_factor_failures","second_factor_locks","token_grant_refusals","access_token_refusals","client_authentication_failures","authorization_refusals","device_code_guesses","suspicious_clients","admin_access_denials","request_screening_refusals"]},"ThreatCategory":{"type":"string","description":"The group a signal is shown under: guessing credentials, guessing the second factor, misusing tokens and clients at the protocol endpoints, registering clients, probing the privileged API, and malformed traffic turned away at the edge.","enum":["credentials","second_factor","tokens","clients","admin","edge"]},"ThreatStatus":{"type":"string","description":"How a signal's count reads against its threshold: `quiet` when nothing was counted, `normal` under every threshold, `elevated` at or over the elevated rate or several times the window before, `critical` at or over the critical rate.","enum":["quiet","normal","elevated","critical"]},"ThreatThreshold":{"type":"object","description":"The recommended measurement a signal is judged by. The rates are per hour over the whole window, so one threshold means the same over a quarter of an hour and over a month; the baseline rule raises the status to `elevated` when the count is at least `baseline_ratio` times the count of the equal window before, once it reaches `baseline_floor`.","required":["elevated_per_hour","critical_per_hour","baseline_ratio","baseline_floor"],"additionalProperties":false,"properties":{"elevated_per_hour":{"type":"number","minimum":0},"critical_per_hour":{"type":"number","minimum":0},"baseline_ratio":{"type":"number","minimum":1},"baseline_floor":{"type":"integer","format":"int64","minimum":0}}},"ThreatFilter":{"type":"object","description":"The audit list's filters that list exactly the records the signal counted, for the drill-down from a tile to the log: values of its `action` and `outcome` parameters. An empty `outcome` means every outcome.","required":["action","outcome"],"additionalProperties":false,"properties":{"action":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/AuditAction"}},"outcome":{"type":"array","maxItems":4,"items":{"$ref":"#/components/schemas/AuditOutcome"}}}},"ThreatReference":{"type":"object","description":"Where the threat a signal counts is described.","required":["framework","id","url"],"additionalProperties":false,"properties":{"framework":{"type":"string","maxLength":64,"description":"`MITRE ATT&CK`, `OWASP Automated Threats`, `RFC 9700`, …"},"id":{"type":"string","maxLength":64,"description":"The technique, threat or section within it."},"url":{"type":"string","format":"uri","maxLength":512}}},"SignInShape":{"type":"object","description":"What the window's failed sign-ins look like together: the three numbers that tell one account hammered from a few addresses, many accounts sprayed from one, and a leaked list tried from many apart. Aggregates only: no address and no account is named.","required":["failures","unresolved","addresses","accounts","pattern"],"additionalProperties":false,"properties":{"failures":{"type":"integer","format":"int64","minimum":0,"description":"Sign-ins refused in the window."},"unresolved":{"type":"integer","format":"int64","minimum":0,"description":"Of those, the attempts that resolved to no account: an address no account holds, or an attempt shed before the lookup."},"addresses":{"type":"integer","format":"int64","minimum":0,"description":"Distinct client addresses the refused sign-ins came from."},"accounts":{"type":"integer","format":"int64","minimum":0,"description":"Distinct accounts the refused sign-ins resolved to."},"pattern":{"$ref":"#/components/schemas/SignInPattern"}}},"SignInPattern":{"type":"string","description":"The attack the shape of the window's failed sign-ins most resembles. `none` when there are too few failures to read a shape in; `credential_cracking` when a few accounts were each tried many times; `password_spraying` when many accounts were tried from a few addresses; `credential_stuffing` when many accounts, or mostly identifiers no account holds, were tried from many addresses; `diffuse` when there are enough failures to look at and no one shape.","enum":["none","credential_cracking","password_spraying","credential_stuffing","diffuse"]},"ClientIPPosture":{"type":"object","description":"How this deployment determines the client address.","required":["mode","trusted_proxies","wide_trusted_proxies","forwarding_proxies","findings"],"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/ClientIPMode"},"header":{"type":"string","maxLength":64,"description":"The header read, for mode `header`."},"trusted_proxies":{"type":"array","maxItems":1024,"description":"The trusted-proxy networks, as configured.","items":{"type":"string","maxLength":64}},"wide_trusted_proxies":{"type":"array","maxItems":1024,"description":"The trusted-proxy networks wider than `security.trusted_proxies_warn_prefix`: every workload inside one can name its own client address.","items":{"type":"string","maxLength":64}},"forwarding_proxies":{"type":"array","maxItems":16,"description":"The trusted proxies that forwarded recent requests, each as a host prefix: what a wide trusted network can be narrowed to. Empty when none did, or more than a few.","items":{"type":"string","maxLength":64}},"findings":{"type":"array","maxItems":1,"description":"The reverse proxy recent traffic shows, if any; empty when there is none.","items":{"$ref":"#/components/schemas/ProxyFinding"}}}},"ClientIPMode":{"type":"string","description":"Where the client address is read from. `socket`: the connecting peer. `x-forwarded-for`: X-Forwarded-For from a trusted proxy, walked right to left. `header`: one single-valued header from a trusted proxy.","enum":["socket","x-forwarded-for","header"]},"ProxyFinding":{"type":"object","description":"A reverse proxy the configuration does not name: most recent requests came from a few private peers that sent a client-address header while the socket address was in use, so every client behind them shares one rate-limit bucket.","required":["samples","proxied","peers","suggested_mode","suggested_trusted_proxies"],"additionalProperties":false,"properties":{"samples":{"type":"integer","minimum":0,"description":"How many recent requests the finding was judged on."},"proxied":{"type":"integer","minimum":0,"description":"How many of them came through a suspected proxy."},"peers":{"type":"array","maxItems":16,"description":"The suspected proxies' addresses.","items":{"type":"string","maxLength":64}},"suggested_mode":{"$ref":"#/components/schemas/ClientIPMode"},"suggested_header":{"type":"string","maxLength":64,"description":"The header to read, when the suggested mode is `header`."},"suggested_trusted_proxies":{"type":"array","maxItems":16,"description":"Each suspected proxy's own address as a host prefix, to add to the trusted-proxy list: a range would also trust every other workload inside it.","items":{"type":"string","maxLength":64}}}}},"parameters":{"CookieHeader":{"name":"Cookie","in":"header","required":false,"description":"The request Cookie header; the interactive login / pending-auth session cookie, and the browser marker a sign-in is recognized by, are read from it.","schema":{"type":"string"}},"AuthorizationHeader":{"name":"Authorization","in":"header","required":false,"description":"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.","schema":{"type":"string"}},"DpopHeader":{"name":"DPoP","in":"header","required":false,"description":"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.","schema":{"type":"string"}},"ClientIdPathParam":{"name":"client_id","in":"path","required":true,"description":"The registered client identifier.","schema":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[!-~]+$"}},"OriginHeader":{"name":"Origin","in":"header","required":false,"description":"Browser Origin, checked (with Sec-Fetch-Site) as a same-origin/CSRF guard.","schema":{"type":"string"}},"SecFetchSiteHeader":{"name":"Sec-Fetch-Site","in":"header","required":false,"description":"Fetch metadata; only same-origin is trusted (CSRF guard).","schema":{"type":"string"}},"ContentTypeJsonHeader":{"name":"Content-Type","in":"header","required":false,"description":"Must be application/json — a JSON body cannot be a CORS simple request, closing form-based CSRF.","schema":{"type":"string"}},"UserAgentHeader":{"name":"User-Agent","in":"header","required":false,"schema":{"type":"string"}},"AcceptLanguageHeader":{"name":"Accept-Language","in":"header","required":false,"description":"The browser's preferred languages (RFC 9110 §12.5.4); a message the operation queues, such as a new-sign-in alert, is written in the first one the server ships.","schema":{"type":"string"}},"LimitQueryParam":{"name":"limit","in":"query","required":false,"description":"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.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},"SortQueryParam":{"name":"sort","in":"query","required":false,"description":"Ordering for the listing: a comma-separated list of fields, most\nsignificant first, a leading `-` marking a descending term. For example\n`sort=last_name,-first_name` sorts by last name ascending, then by first\nname descending.\n\nWhich fields a given endpoint accepts is listed in its `x-sort-fields`\nextension; naming anything else is a 400. A field may appear only once.\n\nThe server always appends the resource's primary key as a final term, so\nthe ordering is TOTAL — rows that tie on every field you named still have\na defined relative order, without which a page could skip or repeat them.\nThe full ordering, including that appended term, is echoed as `page.sort`.\n\nSend it on the FIRST request of a walk only: every cursor records the\nordering it was issued under, so it carries forward automatically, and a\n`sort` that contradicts the supplied `cursor` is rejected with a 400\nrather than silently reordering mid-walk.\n","schema":{"type":"string","maxLength":256,"pattern":"^-?[a-z][a-z0-9_]*(,-?[a-z][a-z0-9_]*)*$"}},"CursorQueryParam":{"name":"cursor","in":"query","required":false,"description":"Opaque keyset cursor identifying where a page begins. Omit it for the\nfirst page; afterwards pass the `next_cursor` (or `prev_cursor`) value\nfrom the previous response verbatim — the token itself records which\ndirection it steps in, so there is no separate direction parameter.\n\nThe token is server-defined and MUST be treated as opaque: it is bound\nto the listing and sort order that issued it, and a cursor that is\nmalformed, tampered with, or belongs to a different listing or order is\nrejected with a 400 rather than silently paging through the wrong set.\n","schema":{"type":"string","maxLength":1024}},"OrgSlugPathParam":{"name":"slug","in":"path","required":true,"description":"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.","schema":{"type":"string","minLength":1,"maxLength":64}},"IfNoneMatchHeader":{"name":"If-None-Match","in":"header","required":false,"description":"The entity tags of the representations the caller already holds (RFC 9110 §13.1.2). When one matches the current representation the answer is 304 with no body.","schema":{"type":"string"}},"ClientRegistrationIdPathParam":{"name":"id","in":"path","required":true,"description":"The client's registration id (UUID), as the clients listing returns it in `id` — not its `client_id`, which another registration in the same organization may also carry.","schema":{"type":"string","minLength":1,"maxLength":64}},"UserIdPathParam":{"name":"id","in":"path","required":true,"description":"The user id (UUID).","schema":{"type":"string","minLength":1,"maxLength":64}},"JustificationQueryParam":{"name":"reason","in":"query","required":true,"description":"Why the administrator is doing this, in their words. It is recorded on the audit event and nowhere else. A query parameter because a DELETE has no body and the server's request logs carry the path only.","schema":{"type":"string","minLength":1,"maxLength":200}},"RolePathParam":{"name":"role","in":"path","required":true,"description":"The role, one of the built-in role ids.","schema":{"$ref":"#/components/schemas/Role"}},"AuditActionFilterParam":{"name":"action","in":"query","required":false,"description":"Action patterns, comma-separated. `*` and `?` are wildcards and do not stop at a dot, so `user.*` covers `user.password.changed` as well as `user.created`. A pattern matching no known action simply returns nothing, and an empty element is ignored. With no pattern, every action but `system.audit.checkpoint` is returned: the hash chain's checkpoint records, which the verifier reads, come back only to a pattern that matches them.","style":"form","explode":false,"x-match":"glob","schema":{"type":"array","maxItems":32,"items":{"type":"string","maxLength":128,"pattern":"^[a-z0-9_.*?-]*$"}}},"AuditCategoryFilterParam":{"name":"category","in":"query","required":false,"description":"Categories, comma-separated. A record is matched by the category it was written under.","style":"form","explode":false,"schema":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/AuditCategory"}}},"AuditOutcomeFilterParam":{"name":"outcome","in":"query","required":false,"description":"Outcomes, comma-separated. `failure,denied,error`, in any order, is every refusal and fault: the base query of most detectors, which has an index of its own.","style":"form","explode":false,"schema":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/AuditOutcome"}}},"AuditSeverityFilterParam":{"name":"severity","in":"query","required":false,"description":"Severities, comma-separated.","style":"form","explode":false,"schema":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/AuditSeverity"}}},"AuditReasonFilterParam":{"name":"reason","in":"query","required":false,"description":"Reasons, comma-separated: the error each refusal answered with, such as `invalid_grant` or `invalid_dpop_proof`. A success carries no reason; ask for successes with `outcome`.","style":"form","explode":false,"schema":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/AuditReason"}}},"AuditActorTypeFilterParam":{"name":"actor_type","in":"query","required":false,"description":"Actor types, comma-separated. `anonymous` is every record made before a caller was identified.","style":"form","explode":false,"schema":{"type":"array","maxItems":8,"items":{"$ref":"#/components/schemas/AuditActorType"}}},"AuditOperationFilterParam":{"name":"operation","in":"query","required":false,"description":"Operations, comma-separated, each matched exactly: the operation a record was made under, as its `operation` field holds it — the operationId as this document spells it, so a record stored by an earlier release, which holds the Go identifier (`GetJWKS`), is found by that spelling.","style":"form","explode":false,"schema":{"type":"array","maxItems":16,"items":{"type":"string","maxLength":256,"pattern":"^[A-Za-z0-9_./-]+$"}}},"AuditActorIdFilterParam":{"name":"actor_id","in":"query","required":false,"description":"Only records caused by this principal.","schema":{"type":"string","maxLength":256}},"AuditTargetIdFilterParam":{"name":"target_id","in":"query","required":false,"description":"Only records acting on this object.","schema":{"type":"string","maxLength":256}},"AuditTargetTypeFilterParam":{"name":"target_type","in":"query","required":false,"schema":{"$ref":"#/components/schemas/AuditTargetType"}},"AuditClientIpFilterParam":{"name":"client_ip","in":"query","required":false,"description":"Only records from this address. Any spelling of an address matches the one the server stores (`2001:DB8::1` is `2001:db8::1`, and `::ffff:203.0.113.5` is `203.0.113.5`); a value that is not an IP address is rejected with a 400.","schema":{"type":"string","maxLength":64}},"AuditClientNetFilterParam":{"name":"client_net","in":"query","required":false,"description":"Only records from addresses in this network: an IPv4 `/24` or an IPv6 `/64`, the unit an address rotates within while its owner stays the same. A single address stands for the network it lies in. Any other prefix length is rejected with a 400. Records written before the server kept the network are not matched.","schema":{"type":"string","maxLength":64}},"AuditClientIdFilterParam":{"name":"client_id","in":"query","required":false,"description":"Only records about this client, whether the server resolved the record to it or the caller only named it. Each record says which: a resolved client is its `client_id`, a named one its `claimed_client_id`.","schema":{"type":"string","maxLength":256}},"AuditSessionIdFilterParam":{"name":"session_id","in":"query","required":false,"schema":{"type":"string","maxLength":256}},"AuditRequestIdFilterParam":{"name":"request_id","in":"query","required":false,"description":"The `X-Request-ID` of a single request. This and `trace_id` are what a support engineer arrives with.","schema":{"type":"string","maxLength":256}},"AuditTraceIdFilterParam":{"name":"trace_id","in":"query","required":false,"schema":{"type":"string","maxLength":64}},"AuditCrossTenantOnlyFilterParam":{"name":"cross_tenant_only","in":"query","required":false,"description":"Only actions taken on this organization by an actor from another one. It is the single most useful security view a multi-tenant audit log offers, which is why it is a first-class filter.","schema":{"type":"boolean"}},"AuditEventIdPathParam":{"name":"id","in":"path","required":true,"description":"The audit record id (UUIDv7). Being time-ordered, it also tells the server which partition to read.","schema":{"type":"string","minLength":1,"maxLength":64}}},"headers":{"Location":{"schema":{"type":"string"}},"CacheControlNoStore":{"schema":{"const":"no-store"}},"PragmaNoCache":{"schema":{"const":"no-cache"}},"SetCookie":{"schema":{"type":"array","items":{"type":"string"}}},"WwwAuthenticate":{"schema":{"type":"string"}},"DpopNonce":{"schema":{"type":"string"}},"VaryAuthorization":{"schema":{"const":"Authorization"}},"VaryCookie":{"schema":{"const":"Cookie"}},"ETag":{"schema":{"type":"string"}},"RetryAfter":{"schema":{"type":"string"}}},"responses":{"RateLimited":{"description":"Too many requests.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"default":{"value":{"type":"about:blank","title":"Too Many Requests","status":429,"detail":"rate limit exceeded"}}}}}},"OAuthErrorResponse":{"description":"OAuth2 error response (RFC 6749 §5.2).","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"WWW-Authenticate":{"$ref":"#/components/headers/WwwAuthenticate"},"DPoP-Nonce":{"$ref":"#/components/headers/DpopNonce"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"DpopChallenge":{"description":"Missing or invalid token (RFC 6750 / RFC 9449 challenge).","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"WWW-Authenticate":{"$ref":"#/components/headers/WwwAuthenticate"},"DPoP-Nonce":{"$ref":"#/components/headers/DpopNonce"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"RegistrationErrorResponse":{"description":"Registration error (RFC 7591 §3.2.2).","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"WWW-Authenticate":{"$ref":"#/components/headers/WwwAuthenticate"},"DPoP-Nonce":{"$ref":"#/components/headers/DpopNonce"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationError"}}}},"ProblemResponse":{"description":"Problem details (RFC 9457).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"ProtectedProblemResponse":{"description":"Problem details (RFC 9457) with an RFC 6750 / RFC 9449 challenge.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControlNoStore"},"WWW-Authenticate":{"$ref":"#/components/headers/WwwAuthenticate"},"DPoP-Nonce":{"$ref":"#/components/headers/DpopNonce"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}