{"openapi":"3.1.0","info":{"title":"ANKASecure Auth API","description":"OAuth 2.0 compliant authentication API for the ANKASecure platform.\n\nThe token endpoint publishes four grant types:\n- authorization_code: redeems the code a sign-in at POST /api/v3/auth/authorize-challenge\n  ended in, with its PKCE code_verifier (the console)\n- client_credentials: application and workload authentication\n- refresh_token: token refresh and tenant switch within a session\n- urn:ietf:params:oauth:grant-type:token-exchange: impersonation (RFC 8693)\n\nAny other grant_type, password included, is refused with unsupported_grant_type. A person\nsigns in through POST /api/v3/auth/authorize-challenge.\n\nAlso implements:\n- RFC 7662: Token Introspection\n- RFC 7009: Token Revocation\n","contact":{"name":"ANKATech Support","url":"https://ankatech.co","email":"info@ankatech.co"},"version":"3.3.0"},"servers":[{"url":"https://{host}:{port}","description":"Parametrized ANKASecure Auth Server (https://host:port)","variables":{"host":{"description":"The hostname or IP address of the server","default":"demo.ankatech.co"},"port":{"description":"The network port on which the server listens","default":"443"}}}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Authorization Server Metadata","description":"The OAuth 2.0 authorization server metadata (RFC 8414): the issuer identifier, the token endpoint,\nthe JWK Set, and the DPoP proof algorithms. No authentication is required.\n"},{"name":"Account Security","description":"The signed-in person's own sign-in factors. A person signs in with passkeys (WebAuthn credentials held by a device, a password manager or a security key), with an authenticator app (TOTP) as the second factor of a password, and, when every other factor is lost, with a single-use recovery code. These operations list the person's authenticators with the capability properties an authentication policy reads, register a passkey, enrol an authenticator app, rename or remove an authenticator, replace the recovery codes and remove the password. Every operation requires an access token, acts only on the person that token names, and is not available while acting on behalf of a tenant. A removal that would leave a membership of the person, or the platform plane, without a way to sign in is refused, and nothing is removed.\n"},{"name":"Account Lifecycle","description":"Account management operations: password reset, self-service password change, and the list of\ntenants a signed-in person may switch to. An account is activated through the sign-in itself\n(submitAuthorizationChallenge with activation_token), where the person enrols a passkey or an\nauthenticator app.\n\nA person is one identity with one credential, used in every tenant the person is a member of, so\nnone of these operations takes a tenant. The two reset operations require no authentication: the\nreset itself proves the caller with a single-use cryptographic token instead. Changing your\nown password requires an access token AND the current password. Listing your memberships requires\nan access token.\n"},{"name":"Token Signing Keys","description":"The public keys that verify the access tokens this service issues, published as a JWK Set\n(RFC 7517). A verifier selects the key by the token's `kid` header and caches the set for the\n`max-age` the response declares. No authentication is required.\n"},{"name":"Platform Identity Sessions","description":"A platform administrator lists and ends the sign-in sessions of any identity, in every tenant. Available on private-cloud and on-premise deployments, to callers signed in to the platform tenant.\n"},{"name":"Tenant User Sessions","description":"A tenant administrator lists and ends the sign-in sessions of a member of their tenant. Only the member's sessions that are currently in the administrator's tenant are shown or ended; the member's sessions in other tenants are neither shown nor touched. The tenant is the one the administrator's access token was issued for.\n"},{"name":"Token Management","description":"OAuth 2.0 token lifecycle management including issuance, introspection, and revocation"},{"name":"Authorization Challenge","description":"The first-party sign-in of the console, one step per request (OAuth 2.0 for First-Party Applications, draft-ietf-oauth-first-party-apps). A sign-in starts with a PKCE challenge, proves the person with a passkey, or with a password (local or directory) and a second factor, selects the tenant when several are open to them, and ends in an authorization code redeemed at the token endpoint with grant_type=authorization_code. The same endpoint onboards a person: an activation link, an invitation or the enrolment token of an assisted recovery starts it, and the passkey or authenticator app is enrolled inside it.\n"},{"name":"Account Sessions","description":"The signed-in person's own sessions. A session begins at a sign-in and keeps its identifier across every refresh and tenant switch, until it is revoked, logged out, inactive for longer than its inactivity window, or past its absolute expiry. The person sees their live sessions in every tenant and can end one of them, every one but the current one, or all of them. Every operation requires an access token and acts only on the person that token names.\n"}],"paths":{"/api/v3/auth/token":{"post":{"tags":["Token Management"],"summary":"OAuth 2.0 token endpoint (RFC 6749/8693)","description":"OAuth 2.0 token endpoint supporting five grant types.\n\n**Request Format:** application/x-www-form-urlencoded (OAuth 2.0 standard)\n\n**Supported Grant Types:**\n- authorization_code: Redeems the authorization code a sign-in at\n  POST /api/v3/auth/authorize-challenge ended in, with its PKCE code_verifier (RFC 6749\n  Section 4.1.3, RFC 7636 Section 4.5). The code is valid for 60 seconds and is consumed by its\n  first presentation; a second presentation, a wrong code_verifier or an expired code is\n  invalid_grant. The answer is the session of the tenant the sign-in admitted; `amr` names the\n  methods of the authority that admitted the membership (`user` and `hwk` or `swk` for a passkey;\n  the methods the OIDC or SAML provider reported, when it admitted). The code is presented\n  without `client_id`, and its refresh token is set in the HttpOnly refresh cookie; a code\n  presented with a `client_id` is invalid_grant. The code of a tenant switch's step-up or of a fresh\n  reauthentication (below) opens no session: it is presented with the refresh token of the\n  session that asked for it (its cookie, or `refresh_token`), and the answer is that session's\n  tokens for the requested tenant (for a reauthentication, its own tenant), with the same `sub`,\n  `sid` and `auth_time`; `amr` adds the methods just proven, and `reauth_time` is the instant of\n  the last proof.\n- client_credentials: Application authentication (RFC 6749 Section 4.4)\n- refresh_token: Token refresh and tenant switch (RFC 6749 Section 6)\n- urn:ietf:params:oauth:grant-type:token-exchange: Impersonation (RFC 8693)\n\nAny other `grant_type`, `password` included, is answered `unsupported_grant_type`.\n\n**A person's session:** its refresh token is set in the HttpOnly refresh cookie and\n`refresh_token` is null in the body. It is presented without `client_id`: a person's refresh\ntoken presented with any `client_id` is invalid_grant.\n\n**One identity, several tenants:** a person is one identity holding a membership in each\ntenant they belong to, and every human token's `sub` is the identity id; `tenantId` is the\nmembership in use.\n- A `refresh_token` grant carrying the `tenant_id` of another ACTIVE membership switches to it:\n  a new access and refresh token for the target tenant, the same `sub`, the presented refresh\n  token rotated. A refused switch is `invalid_grant` and leaves the presented refresh token\n  usable for its own tenant.\n- A switch is decided on the evidence the\n  session was authenticated with, against the target tenant's authentication posture. Evidence that satisfies it switches with no prompt, and\n  switching back to a tenant entered earlier needs no new proof. Evidence that does not satisfy it\n  is answered `403 insufficient_authorization` with an `auth_session`\n  (draft-ietf-oauth-first-party-apps): nothing is issued or rotated, and the presented refresh\n  token stays usable. The client continues at `POST /api/v3/auth/authorize-challenge` with that\n  `auth_session`, a new S256 `code_challenge` and no `action`; only proofs of the session's own\n  identity are accepted there, and a tenant that admits its members through an identity provider\n  only is reached through that provider. The step ends in an authorization code redeemed as\n  described above.\n- Every refresh and switch is minted from the target tenant's own facts, read from\n  the authentication backend at that moment. In a tenant whose single sign-on is enforced, a\n  membership linked to an identity provider is reached only by a session authenticated\n  through that provider.\n- A session whose record does not state how it was opened (an authorization code, an\n  emergency access or a federated sign-in) never refreshes or switches: the\n  `refresh_token` grant answers it as a session past its absolute lifetime (invalid_grant,\n  \"Refresh token is no longer valid. Please re-authenticate.\"), and the session ends, with every\n  access token it issued. The person signs in again at POST /api/v3/auth/authorize-challenge.\n- A `refresh_token` grant carrying `max_age` (a non-negative integer of seconds, without a\n  switch to another tenant) asks for a recent proof of the person. When the session's most\n  recent authentication for its tenant is older than `max_age`, the answer is\n  `403 insufficient_authorization` with an `auth_session`: nothing is issued or rotated, and the\n  presented refresh token stays usable. The client continues the fresh reauthentication at\n  `POST /api/v3/auth/authorize-challenge` with that `auth_session`, a new S256 `code_challenge`\n  and no `action`. It re-proves the authority that admits the tenant, on its own proofs only:\n  a passkey, a password and a TOTP code where the session was admitted with them, or the\n  tenant's identity provider. The authorization code it ends in is redeemed with\n  `grant_type=authorization_code`, the `code_verifier` and the session's refresh token: the\n  same session is rotated and its tokens carry a new `reauth_time`. A session that holds no\n  such proof (a password alone) receives `invalid_grant` and renews its proof by signing in\n  again. With a recent enough proof the grant is an ordinary refresh. `max_age` on the\n  refresh grant departs from RFC 6749, which defines no such parameter there: the refresh\n  token is the only credential bound to the session that the client presents, so it is where\n  the request for a fresh proof is made, as draft-ietf-oauth-first-party-apps allows a refresh\n  to be answered with `insufficient_authorization`.\n\n**Security:**\n- Rate limited, and also operator-tunable: `auth.rate-limit.login-attempts-per-minute`\n  and `auth.rate-limit.requests-per-minute` are platform settings this service reloads\n  live. The shipped defaults are 10 attempts/min for the client_credentials grant and\n  30/min for the other grants; a deployment may have changed either\n- Tokens are JWTs signed with RS256 (RSA-3072)\n\n**Token Claims:**\n- HUMAN tokens: audience=ankasecure-admin, includes loginId (email)\n- APPLICATION tokens: audience=ankasecure-core, includes kp/kpv (key permissions)\n\nAll tokens include: sub, iss, aud, exp, iat, jti, tenantId, scopes, userType.\nHuman session tokens also carry `auth_method`: `pwd` for a password session, `fed:{idpId}`\nfor a session established through an identity provider; `sid`, `auth_time` (the original\nsign-in, which never moves), `amr`, and `reauth_time`: the most recent proof of identity the\nsession holds for the token's tenant, in epoch seconds. A sign-in proven by local or directory\nauthenticators sets it to `auth_time`; a sign-in through an OIDC or SAML provider sets it to the\ninstant the provider stated it authenticated the person (`auth_time` of its ID token,\n`AuthnInstant` of its assertion), never the instant of the callback, and omits it when the\nprovider stated none. The session's `auth_time` stays the sign-in instant either way. A fresh\nreauthentication advances `reauth_time`, a plain refresh never does.\n\n**Impersonation tokens (token exchange, RFC 8693):** `act` names the operator (`sub`,\n`tenant_id`, `roles`, `username`) and `act.sid`, the operator's session the `subject_token`\nbelongs to; `reauth_time` is the subject token's, unchanged; `impersonation` is `true` and the\nouter `roles` is the tenant role mirrored from the operator's platform role. No refresh token\nis issued. The token lives at most 900 seconds, never past the consent grant it rides or the\nabsolute expiry of the operator's session, and the end of that session (logout, revocation)\nrevokes it. `sub` is the operator's own identity id.\n\n**Administrative workloads (DPoP-bound tokens, RFC 9449):** a `client_credentials` request\nthat carries a `DPoP` header is an administrative workload's. `client_id` is the workload id,\nand the workload authenticates with a `client_assertion` only — signed by a key registered on\nit (`urn:ietf:params:oauth:client-assertion-type:jwt-bearer`, single-use) or minted for it by\na trusted external issuer (`urn:ankatech:params:oauth:client-assertion-type:federated-jwt`).\nThe `DPoP` header carries exactly one proof (`typ` `dpop+jwt`, alg ES256, ES384, PS256 or\nRS256, a public `jwk`, `htm` `POST`, `htu` the public token endpoint URL published in the\nauthorization server metadata, `iat` within the last 60 seconds, a fresh `jti`).\n- The answer is `{\"access_token\", \"token_type\": \"DPoP\", \"expires_in\": 300}` with\n  `Cache-Control: no-store` and no refresh token. The token (`typ` `at+jwt`) is for the\n  administrative API only (`aud` `ankasecure-admin`), names the workload as `sub` and\n  `client_id`, carries the workload's literal `scopes` and is bound to the proof key\n  (`cnf.jkt`): every request with it carries `Authorization: DPoP` and a new proof.\n- A proof that is missing, repeated, malformed or stale is `400 invalid_dpop_proof`; no\n  credential is examined. Every credential refusal is the same `401 invalid_client`.\n- The authorization server metadata (`GET /api/v3/auth/.well-known/oauth-authorization-server`)\n  lists the proof algorithms in `dpop_signing_alg_values_supported`.\n- Without a `DPoP` header the grant authenticates a cryptographic actor and answers a Bearer\n  token; a workload credential never yields a Bearer token.\n","operationId":"createToken","parameters":[{"name":"DPoP","in":"header","description":"A DPoP proof (RFC 9449 §4), JWS compact serialization. Sent by an administrative workload with grant_type=client_credentials to obtain a DPoP-bound token; exactly one such header. Absent on every other request.","schema":{"type":"string"},"example":"eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoibDhUM0x0dkJmWjJ2cnQ3MEtXaFlFemRTN3lWLXJSZDVRMWRJd0JaMlRlRSIsInkiOiJDYXFPS05zYkVkckJ1VlN0dDJxeFp4bVZvZU1NRVctS0pKWGRVSm1hTjRrIn19.eyJqdGkiOiI0YjNlNjRjYS1iMjIzLTRiNmItOWM0YS1mYjM5YjE5NmZjYjEiLCJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9kZXYuYW5rYXRlY2guY28vYXBpL3YzL2F1dGgvdG9rZW4iLCJpYXQiOjE3OTA1MDMyMDB9.Bq7l2wN3o0Ye2b8rZpBqv0c5aV7dHtTqIuX4cC2zO9P1mF8kY6sR3uE0wA9nL5jG7hD4fS2eB1vK8xQ3yZ6tWg"}],"requestBody":{"description":"OAuth 2.0 token request with grant_type parameter","content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/TokenRequest"},"examples":{"1. Console sign-in completion (Authorization Code Grant)":{"summary":"Redeem the authorization code of a console sign-in with its PKCE verifier","description":"1. Console sign-in completion (Authorization Code Grant)","value":"grant_type=authorization_code&code=uM3rQ9xT2kW7vB5nY1cL8hJ4gF6dS0aZ_pE3oN7iR2k&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"},"2. Application Login (Client Credentials Grant)":{"summary":"Authenticate application with client_id/client_secret","description":"2. Application Login (Client Credentials Grant)","value":"grant_type=client_credentials&client_id=550e8400-e29b-41d4-a716-446655440000&client_secret=c8d9e7f6a5b4c3d2e1f0a9b8c7d6e5f4"},"2a. Administrative workload login (Client Credentials Grant + DPoP header)":{"summary":"A workload's private_key_jwt assertion, sent with a DPoP header","description":"2a. Administrative workload login (Client Credentials Grant + DPoP header)","value":"grant_type=client_credentials&client_id=9b2f6c1e-3d4a-4c8b-a1e7-5f0d2c9b8a71&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6Ijl0bVhLb3ZtZ1Z6QzZsQjhkb1ZPRHB1Y1NLX3JfN0JXc3BnYXVrN1Rmc28ifQ.eyJpc3MiOiI5YjJmNmMxZS0zZDRhLTRjOGItYTFlNy01ZjBkMmM5YjhhNzEiLCJzdWIiOiI5YjJmNmMxZS0zZDRhLTRjOGItYTFlNy01ZjBkMmM5YjhhNzEifQ.MEUCIQ"},"3. Token Refresh (Refresh Token Grant)":{"summary":"Renew the console session's access token; the refresh token rides the cookie","description":"3. Token Refresh (Refresh Token Grant)","value":"grant_type=refresh_token"},"3a. Tenant Switch (Refresh Token Grant)":{"summary":"Switch the session to another ACTIVE membership of the same person","description":"3a. Tenant Switch (Refresh Token Grant)","value":"grant_type=refresh_token&tenant_id=00000000-0000-0000-0000-000000000004"},"4. Token Exchange (Impersonation)":{"summary":"Exchange platform admin token for tenant admin token","description":"4. Token Exchange (Impersonation)","value":"grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token=eyJhbGc...&subject_token_type=urn:ietf:params:oauth:token-type:access_token&requested_token_type=urn:ietf:params:oauth:token-type:access_token&resource=urn:ankatech:tenant:00000000-0000-0000-0000-000000000003"}}}},"required":true},"responses":{"200":{"description":"Token issued successfully. Every grant answers with the token response format (RFC 6749 §5.1); the token-exchange grant adds `issued_token_type` and `scope` and omits `passwordChangeEligible` (RFC 8693 §2.2.1).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"},"examples":{"Console Sign-in Success":{"summary":"Successful authorization_code grant of a console sign-in: the refresh token is in the cookie","description":"Console Sign-in Success","value":{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...","refresh_token":null,"token_type":"Bearer","expires_in":900,"passwordChangeEligible":false}},"Application Login Success":{"summary":"Successful client_credentials grant (app login)","description":"Application Login Success","value":{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...","token_type":"Bearer","expires_in":900}},"Administrative Workload Login Success":{"summary":"Successful client_credentials grant with a DPoP proof: a DPoP-bound token","description":"Administrative Workload Login Success","value":{"access_token":"eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ikl1ZEFPcXU5OURQeGFXMWVCVjlRSXhqc3Z5NG5UZHFCRVBFQ2w3Wnp6ckEifQ...","token_type":"DPoP","expires_in":300}},"Token Refresh Success":{"summary":"Successful refresh_token grant, plain or switching tenant","description":"Token Refresh Success","value":{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...","token_type":"Bearer","expires_in":900}},"Token Exchange Success":{"summary":"Successful RFC 8693 token-exchange grant (impersonation)","description":"Token Exchange Success","value":{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...","issued_token_type":"urn:ietf:params:oauth:token-type:access_token","token_type":"Bearer","expires_in":900,"scope":"admin.tenant.user.read admin.tenant.user.create admin.tenant.analytics.read"}}}}}},"400":{"description":"Authentication failed - invalid_grant (RFC 6749 §5.2). invalid_grant covers an authorization code that is unknown, reused, expired, or presented with a wrong code_verifier or with a client_id; an expired or revoked refresh token; a person's refresh token presented with a client_id; a refused tenant switch (which leaves the presented refresh token usable); and a token exchange whose operator holds no user-verifying passkey and whose platform session was not admitted through an approved provider, which no reauthentication can satisfy. Byte-identical response shape across the causes of each grant to prevent enumeration oracles (OWASP A09).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuth2ErrorResponse"},"examples":{"Authorization Code Refused":{"summary":"OAuth 2.0 error: invalid_grant (wrong code_verifier, a client_id presented, reused or expired code)","description":"Authorization Code Refused","value":{"error":"invalid_grant","error_description":"The authorization code is invalid or has expired. Sign in again.","error_uri":"https://docs.ankatech.co/errors/invalid_grant","type":"https://docs.ankatech.co/errors/invalid_grant","title":"Invalid Grant","status":400,"detail":"The authorization code is invalid or has expired. Sign in again.","instance":"/api/v3/auth/token","timestamp":1737630000}},"Expired Refresh Token":{"summary":"OAuth 2.0 error: invalid_grant (expired token)","description":"Expired Refresh Token","value":{"error":"invalid_grant","error_description":"Refresh token has expired","error_uri":"https://docs.ankatech.co/errors/invalid_grant","type":"https://docs.ankatech.co/errors/invalid_grant","title":"Invalid Grant","status":400,"detail":"Refresh token has expired","instance":"/api/v3/auth/token","timestamp":1737630000}},"Refresh Token Presented By Another Client":{"summary":"OAuth 2.0 error: invalid_grant (a person's refresh token presented with a client_id)","description":"Refresh Token Presented By Another Client","value":{"error":"invalid_grant","error_description":"The refresh token was not issued to this client. Sign in again.","error_uri":"https://docs.ankatech.co/errors/invalid_grant","type":"https://docs.ankatech.co/errors/invalid_grant","title":"Invalid Grant","status":400,"detail":"The refresh token was not issued to this client. Sign in again.","instance":"/api/v3/auth/token","timestamp":1737630000}},"Tenant Switch Refused":{"summary":"OAuth 2.0 error: invalid_grant (no ACTIVE membership admitted in the requested tenant)","description":"Tenant Switch Refused","value":{"error":"invalid_grant","error_description":"The requested tenant is not available to this session.","error_uri":"https://docs.ankatech.co/errors/invalid_grant","type":"https://docs.ankatech.co/errors/invalid_grant","title":"Invalid Grant","status":400,"detail":"The requested tenant is not available to this session.","instance":"/api/v3/auth/token","timestamp":1737630000}}}}}},"415":{"description":"Unsupported Media Type - must use application/x-www-form-urlencoded (returned as invalid_request per OAuth 2.0)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuth2ErrorResponse"},"examples":{"Wrong Content-Type":{"summary":"OAuth 2.0 error: invalid_request (wrong Content-Type)","description":"Wrong Content-Type","value":{"error":"invalid_request","error_description":"Content-Type 'application/json' is not supported. Supported types: [application/x-www-form-urlencoded]","error_uri":"https://docs.ankatech.co/errors/invalid_request","type":"https://docs.ankatech.co/errors/invalid_request","title":"Invalid Request","status":400,"detail":"Content-Type 'application/json' is not supported. Supported types: [application/x-www-form-urlencoded]","instance":"/api/v3/auth/token","timestamp":1737630000}}}}}},"401":{"description":"Two causes. `invalid_client` (RFC 6749 section 5.2): client authentication failed on the `client_credentials` grant, an actor's credentials or an administrative workload's client assertion; every credential refusal is the same answer, whatever was wrong. An invalid, expired or revoked `subject_token` is not this answer: it is the `400 invalid_grant` of the token exchange. `insufficient_user_authentication` (RFC 9470 section 3), token-exchange (impersonation) grant only: entering a tenant requires the operator's own session (the `sid` of `subject_token`) to hold an authentication made within the reauthentication window (`auth.reauthentication.max-age-seconds`, 300 seconds by default) with a user-verifying passkey or through an identity provider approved for the platform. A password or a TOTP code never qualifies, and the target tenant's own authentication settings are not consulted. The answer carries `WWW-Authenticate: Bearer error=\"insufficient_user_authentication\", error_description=\"...\", max_age=\"<seconds>\"` and `Cache-Control: no-store`; it is decided before the target tenant is examined, so it says nothing about it. Nothing is issued and no support session is opened: the operator reauthenticates (`max_age` on the `refresh_token` grant) and retries. An operator who could make no such proof (no user-verifying passkey, and a platform session not admitted through an approved provider) is answered `400 invalid_grant` instead, naming that requirement.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuth2ErrorResponse"},"examples":{"Enter a tenant with a session last proven by a password":{"summary":"The platform session signed in with a password 20 minutes ago","description":"Enter a tenant with a session last proven by a password","value":{"error":"insufficient_user_authentication","error_description":"Entering a tenant requires a recent authentication with a passkey or an approved identity provider. Reauthenticate and retry; nothing was changed.","error_uri":"https://docs.ankatech.co/errors/reauthentication-required","type":"https://docs.ankatech.co/errors/reauthentication-required","title":"Reauthentication Required","status":401,"detail":"Entering a tenant requires a recent authentication with a passkey or an approved identity provider. Reauthenticate and retry; nothing was changed.","instance":"/api/v3/auth/token","timestamp":1790505600}}}}}},"403":{"description":"`refresh_token` grant only (`insufficient_authorization`, draft-ietf-oauth-first-party-apps section 5.2.2.1). Either the grant carries `tenant_id` and the requested tenant's authentication posture requires more than the evidence the session was authenticated with; or it carries `max_age` and the session's most recent authentication for its tenant is older than that. Nothing is issued or rotated and the presented refresh token stays usable for its own tenant. `auth_session` is single use, bound to the session and the tenant, and continued at `POST /api/v3/auth/authorize-challenge` with a new S256 `code_challenge` and no `action`. `Cache-Control: no-store`. A tenant the identity holds no ACTIVE, admitted membership in is the `400 invalid_grant` of every refused switch, never this answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuth2ErrorResponse"},"examples":{"Switch into a tenant that requires a passkey":{"summary":"The session signed in with a password and a TOTP code; the tenant admits passkeys only","description":"Switch into a tenant that requires a passkey","value":{"error":"insufficient_authorization","error_description":"The requested tenant requires further authentication. Continue at the authorization challenge endpoint with this auth_session and a new S256 code_challenge.","error_uri":"https://docs.ankatech.co/errors/insufficient_authorization","type":"https://docs.ankatech.co/errors/insufficient_authorization","title":"Insufficient Authorization","status":403,"detail":"The requested tenant requires further authentication. Continue at the authorization challenge endpoint with this auth_session and a new S256 code_challenge.","instance":"/api/v3/auth/token","timestamp":1790503200,"auth_session":"jT4Wq7Zp2Lk9Rv1Xc6Bn3Md8Fs5Gh0Yu2Ei7Oa4Tw9"}},"Fresh reauthentication: the session's last proof is older than max_age":{"summary":"grant_type=refresh_token with max_age=300; the person signed in 40 minutes ago","description":"Fresh reauthentication: the session's last proof is older than max_age","value":{"error":"insufficient_authorization","error_description":"The session's most recent authentication is older than max_age. Continue at the authorization challenge endpoint with this auth_session and a new S256 code_challenge.","error_uri":"https://docs.ankatech.co/errors/insufficient_authorization","type":"https://docs.ankatech.co/errors/insufficient_authorization","title":"Insufficient Authorization","status":403,"detail":"The session's most recent authentication is older than max_age. Continue at the authorization challenge endpoint with this auth_session and a new S256 code_challenge.","instance":"/api/v3/auth/token","timestamp":1790505600,"auth_session":"pR8vN2kX5wQ1mZ7cT4yH9bL3fJ6dS0gA2eU5iO8nW1"}}}}}},"409":{"description":"Token-exchange (impersonation) grant only — this operator already holds a LIVE impersonation session under the tenant's consent grant, and a grant admits at most one, so the tenant's view of who is acting as them stays unambiguous (RFC 7807 Problem Details). Also returned to the loser of two concurrent exchanges, which receives no access token at all. NOT an outage and NOT retryable: the slot is held until the existing session ends or lapses at its own expiry. No other grant type can produce this status.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"},"examples":{"Impersonation Session Already Live":{"summary":"A second support session attempted under the same consent grant","description":"Impersonation Session Already Live","value":{"type":"https://docs.ankatech.co/errors/impersonation-session-already-live","title":"Impersonation Session Already Live","status":409,"detail":"A live impersonation session already exists for this operator under this authorisation. End the existing session before opening another.","instance":"/api/v3/auth/token"}}}}}},"502":{"description":"The administration API, which verifies credentials for this service, is unavailable — it answered 5xx, could not be reached, its circuit breaker is open, or its answer was invalid (RFC 7807 Problem Details). Principal-independent: this response carries no information about the submitted credentials. A refusal of the submitted credentials is never a 502. Any 4xx the backend answers on this call is a fault of the platform, not of the request, and is reported here. The Retry-After header (integer seconds) is present ONLY while the circuit breaker toward the administration API is OPEN. The `detail` names that upstream by its role: the administration service.","headers":{"Retry-After":{"description":"Seconds to wait before retrying; present only while the circuit breaker toward the administration API is OPEN","style":"simple","schema":{"type":"integer","example":27}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"},"examples":{"Administration API unavailable":{"summary":"Login while the authentication backend is down","description":"Administration API unavailable","value":{"type":"https://docs.ankatech.co/errors/upstream-unavailable","title":"Upstream Service Unavailable","status":502,"detail":"The administration service is temporarily unavailable.","instance":"/api/v3/auth/token"}}}}}},"504":{"description":"The administration API, which verifies credentials for this service, did not respond within the fixed login deadline (RFC 7807 Problem Details). The deadline is principal-independent — the same budget applies to every caller, so response timing discloses nothing about account existence. The Retry-After header is present ONLY while the circuit breaker toward the administration API is OPEN. The `detail` names that upstream by its role: the administration service.","headers":{"Retry-After":{"description":"Seconds to wait before retrying; present only while the circuit breaker toward the administration API is OPEN","style":"simple","schema":{"type":"integer","example":27}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"},"examples":{"Administration API timeout":{"summary":"Login exceeded the fixed deadline toward the administration API","description":"Administration API timeout","value":{"type":"https://docs.ankatech.co/errors/upstream-timeout","title":"Upstream Service Timeout","status":504,"detail":"The administration service did not respond in time.","instance":"/api/v3/auth/token"}}}}}},"429":{"description":"The per-source rate limit of this grant type was exceeded (RFC 6585 §4): the caller sent too many token requests in the current one-minute window. Applies per client source, so other callers are unaffected. Retry after `Retry-After`.","headers":{"Retry-After":{"description":"Seconds until the rate-limit window resets.","style":"simple","schema":{"type":"string","example":"60"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Per-source rate limit exceeded":{"summary":"Too many sign-in attempts from one source within a minute","description":"Per-source rate limit exceeded","value":{"type":"https://docs.ankatech.co/errors/too-many-requests","title":"Too Many Requests","status":429,"detail":"Rate limit exceeded. You have exceeded the allowed 5 requests per minute. Please try again in 60 seconds.","instance":"/api/v3/auth/token","extensions":{"retryAfter":60,"limit":5,"window":"1 minute"}}}}}}},"503":{"description":"The service cannot issue a token right now; the refusal is never a verdict about the submitted credential, and nothing was consumed or rotated. Three types:\n\n- **`temporarily_unavailable`** — the concurrent credential-verification bound is reached (`client_credentials` grant; server-wide, the request is shed before its credential is examined — RFC 9110 §15.6.4 temporary overload, RFC 6749 §4.1.2.1 `temporarily_unavailable`). Retry after `Retry-After`, with backoff.\n- **`token-signing-unavailable`** — the token signing key set is unavailable, so no token can be issued. Every grant is refused before its credential is examined, and a refresh token presented during the outage still works afterwards.\n- **`/problems/dependency-not-ready`** — the authentication audit record of this request could not be confirmed, or a recent refusal holds the audit plane in its recent-failure state. Every outcome gets this same answer at the same point — a valid credential, a wrong one, an unknown code — so it says nothing about the credential. No access or refresh token is returned or stored, and a presented refresh token is not rotated. In the recent-failure state an `authorization_code` grant is redeemed and then answered this way, while every other grant is answered before its credential is examined. The one exception is the code of an emergency access (`purpose=break_glass` at the authorization challenge endpoint), whose break-glass session is never refused for its audit. The same type answers an authorization code redemption while the store of codes cannot be read or written: nothing is consumed or issued. The same type also answers every grant, before its credential is examined, while the session and revocation store is being re-established after it lost its data: a token issued then would be refused by every resource server. Retry after `Retry-After`; tokens issued before the loss are no longer accepted, so sign in again.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"1"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Credential verification bound reached":{"summary":"A burst of token requests was shed before any credential was verified","description":"Credential verification bound reached","value":{"error":"temporarily_unavailable","error_description":"The server is temporarily unable to verify client credentials. Retry shortly.","error_uri":"https://docs.ankatech.co/errors/temporarily_unavailable","type":"https://docs.ankatech.co/errors/temporarily_unavailable","title":"Temporarily Unavailable","status":503,"detail":"The server is temporarily unable to verify client credentials. Retry shortly.","instance":"/api/v3/auth/token","timestamp":1790503200}},"Signing key set unavailable":{"summary":"No signing key set is held; the grant was not attempted","description":"Signing key set unavailable","value":{"type":"https://docs.ankatech.co/errors/token-signing-unavailable","title":"Token Signing Unavailable","status":503,"detail":"The token signing key set is temporarily unavailable; no token can be issued or published.","instance":"/api/v3/auth/token","timestamp":1790503200}},"Authentication audit not confirmed":{"summary":"The audit record could not be confirmed; no token was issued","description":"Authentication audit not confirmed","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/token"}}}}}},"500":{"description":"An internal fault of this service, which is never a verdict about the request. Only typed refusals are answered 400 invalid_grant; any other failure while a grant is processed (including during an RFC 8693 token exchange, after the subject_token was admitted) is answered with this fixed body. The fault's own text is logged and never returned, and nothing is issued.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Internal fault during a token exchange":{"summary":"An unanticipated fault; its text is not returned","description":"Internal fault during a token exchange","value":{"type":"https://docs.ankatech.co/errors/internal","title":"Internal Server Error","status":500,"detail":"An unexpected error occurred.","instance":"/api/v3/auth/token","timestamp":1790503200}}}}}}},"security":[]}},"/api/v3/auth/token/revoke":{"post":{"tags":["Token Management"],"summary":"Token revocation (RFC 7009)","description":"Revokes an access token or a refresh token, per OAuth 2.0 RFC 7009.\n\n**Both token planes are supported.** RFC 7009 §2 makes refresh-token revocation the\nmandatory half. A JWT access token is blacklisted by its `jti` until it expires; an\nopaque refresh token is deleted outright. The optional `token_type_hint` only\n**reorders** the search and never ends it (§2.1), so a wrong hint still revokes; an\nunrecognized or empty hint is treated as absent and is never an error.\n\n**Authorization is OWNERSHIP, not a scope.** Any authenticated caller may reach this\nendpoint, and may revoke only a token issued to itself (§2.1). A token belonging to\nanother principal is silently left alone — including for a platform administrator, who\ngains no privilege here. `DELETE /api/v3/auth/token/s/{tokenId}` remains the only\nadministrative revocation path.\n\n**Request format** — `application/x-www-form-urlencoded`, as RFC 7009 §2.1 specifies.\nA JSON body is answered `400` carrying the RFC 6749 §5.2 error object, not `415` — see\nthe 400 response below.\n\n**Outcomes** — per §2.2 the response is `200` with an empty body when the token was\nrevoked, was already gone, never existed, was not the caller's, or was not parseable.\nThese are byte-identical on purpose: a distinguishable refusal would let any caller use\nthis endpoint to discover whether a token value exists and whom it belongs to.\n\n**The one exception is `503`**, returned when the revocation store is unavailable. RFC\n7009 §2.2: on a 503 the client must assume the token still exists. It is decided before\nany per-token outcome is known, so it discloses nothing — and answering 200 there would\nreport a revocation that did not happen.\n","operationId":"revokeToken","requestBody":{"description":"Form-encoded revocation request (RFC 7009 §2.1)","content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/RevokeRequest"},"examples":{"1. Revoke an access token":{"summary":"Blacklisted by jti until it expires","description":"1. Revoke an access token","value":"token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3YzllNjY3OSJ9.sig"},"2. Revoke a refresh token (the RFC 7009 §2 MUST)":{"summary":"The opaque value is deleted outright","description":"2. Revoke a refresh token (the RFC 7009 §2 MUST)","value":"token=Zm9vYmFyYmF6cXV4MTIzNDU2&token_type_hint=refresh_token"},"3. Refresh token with the WRONG hint":{"summary":"Still revoked - the search extends to the other plane","description":"3. Refresh token with the WRONG hint","value":"token=Zm9vYmFyYmF6cXV4MTIzNDU2&token_type_hint=access_token"}}}},"required":true},"responses":{"400":{"description":"The token parameter is absent or blank, the body was not application/x-www-form-urlencoded, OR a credential parameter (token, client_secret, refresh_token, subject_token, client_assertion, invitation_token or another credential) was sent in the query string, which is refused before the caller is authenticated or the token examined. Carries the RFC 6749 §5.2 error object (`{\"error\": \"invalid_request\", ...}`), NOT problem+json — RFC 7009 §2.2.1 and RFC 7662 §2.3 both specify that shape for these endpoints, and this endpoint answers every cause in it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuth2ErrorResponse"}}}},"401":{"description":"No access token was presented, or the one presented is not accepted. A person's token is challenged with `WWW-Authenticate: Bearer`. An administrative workload's token is accepted only under `Authorization: DPoP` with a proof bound to the token and to this request (RFC 9449); otherwise the challenge is `WWW-Authenticate: DPoP error=\"invalid_token\"` (the token was presented under `Bearer`, or is not bound to a key) or `WWW-Authenticate: DPoP error=\"invalid_dpop_proof\"` (the proof is missing, malformed, already used, or names another method or URL). The body is the same in every case.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"`forbidden`: the caller is an administrative workload. A workload's token reaches the token administration operations only; it revokes a token with `forceRevokeToken`, never here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Two causes. **The revocation store is unavailable** — an EMPTY body; per RFC 7009 §2.2 the client must assume the token still exists and retry. **The revocation's audit record could not be confirmed** — the `/problems/dependency-not-ready` problem with `Retry-After`: the revocation WAS applied and the token is refused from now on; only the acknowledgement is withheld. Either way, retrying is safe.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed (audit refusal only).","style":"simple","schema":{"type":"string","example":"30"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Revocation audit not confirmed":{"summary":"The token was revoked; the audit record could not be confirmed","description":"Revocation audit not confirmed","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/token/revoke"}}}}}},"200":{"description":"Token revocation processed (always returns 200 per RFC 7009)"}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/token/logout":{"post":{"tags":["Token Management"],"summary":"Logout and clear refresh token cookie","description":"Revokes the current access token, revokes the session's refresh token and clears the\nrefresh token cookie.\n\nCall this endpoint before client-side cleanup to ensure server-side\ntoken revocation and cookie deletion.\n\n**Security:**\n- Revokes the refresh token carried by the refresh token cookie: the same value presented\n  to the `refresh_token` grant afterwards is refused with `invalid_grant`. A request\n  without the cookie revokes no refresh token\n- Ends the sign-in session that refresh token belongs to, and no other: the session's\n  access tokens are refused from then on and it leaves the session list, while the\n  person's other sessions keep working\n- Clears httpOnly refresh token cookie\n- Revokes access token (adds to blacklist)\n- Works even with expired access tokens\n\n**Signing key set unavailable:** when the access token cannot be checked because this\nservice has no signing key set, the answer is `503` and the access token was not\nrevoked: the client must assume it is still live and retry (RFC 7009 §2.2 gives the\nsame instruction for revocation). The refresh token carried by the cookie was revoked\nand the cookie is cleared.\n\n**Revocation store unavailable:** when the store refuses a refresh token delete, the\nanswer is `503` (`revocation-store-unavailable`), the access token was not revoked, the\nrefresh token cookie is kept so the retry carries it, and at least one presented\nrefresh token is still valid. When the store refuses the\nblacklist write instead, the refresh token was revoked and the cookie is cleared, but\nthe access token is still valid. Either way the client must retry before treating the\nsession as ended.\n","operationId":"logoutSession","parameters":[{"name":"Authorization","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Logout completed, cookie cleared: the refresh token carried by the cookie and the access token presented were revoked. A request with nothing to revoke — no refresh token cookie, no bearer token, or a bearer value that is not a token this service issued — is also 204. A token presented under `DPoP` is never revoked here: a workload's is the 403, and while the signing key set is unavailable it cannot be recognised and the answer is 204 with nothing revoked"},"403":{"description":"`forbidden` — the presented token is an administrative workload's. A workload has no session to sign out of; nothing was revoked and no cookie was cleared. The type is read from the verified token, so while the signing key set is unavailable a workload token presented under `DPoP` cannot be recognised: that request is answered 204, revokes nothing and clears the cookie (under `Bearer` it is the `token-signing-unavailable` 503).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"400":{"description":"`invalid_request` — the request carries more than 8 distinct refresh token cookie values. Nothing was revoked and the refresh token cookie is kept. Repeated identical values count once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuth2ErrorResponse"},"examples":{"Too many distinct refresh token cookies":{"description":"Too many distinct refresh token cookies","value":{"error":"invalid_request","error_description":"The request carries more than 8 distinct refresh token cookies","error_uri":"https://docs.ankatech.co/errors/invalid_request","type":"https://docs.ankatech.co/errors/invalid_request","title":"Invalid Request","status":400,"detail":"The request carries more than 8 distinct refresh token cookies","instance":"/api/v3/auth/token/logout"}}}}}},"503":{"description":"Three causes, told apart by `type`. **`token-signing-unavailable`** — the token signing key set is unavailable, so the access token presented under `Bearer` could not be checked and was NOT revoked; the refresh token carried by the cookie WAS revoked (`Retry-After: 30`). A token presented under `DPoP` gets no such 503: it is answered 204 with nothing revoked, because a workload's token cannot be recognised without the key set. **`revocation-store-unavailable`** (`Retry-After: 5`) — the revocation store refused a write. On a refresh token delete, the refresh token cookie is KEPT so the retry carries it, at least one presented refresh token is still valid, and the access token was not revoked; on the blacklist write, the refresh token WAS revoked and the cookie is cleared, but the access token is still valid. **`/problems/dependency-not-ready`** — either the session store could not end the session, in which case nothing was revoked and the refresh token cookie is KEPT; or the logout's audit record could not be confirmed: the presented tokens WERE revoked and are refused from now on; only the acknowledgement is withheld. The refresh token cookie is cleared on every other 503. Retry after `Retry-After`.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"30"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Logout audit not confirmed":{"summary":"The token was revoked; the audit record could not be confirmed","description":"Logout audit not confirmed","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/token/logout"}}}}}}},"security":[]}},"/api/v3/auth/token/introspect":{"post":{"tags":["Token Management"],"summary":"Token introspection (RFC 7662)","description":"Reports whether a token is currently active, per OAuth 2.0 RFC 7662.\n\n**What `active` means** (RFC 7662 §2.2) — the token was issued by *this* authorization\nserver, has not been revoked, and is within its validity window. That is the whole\ndetermination. A token issued before the revocation store last lost its data counts as\nrevoked, as it does at every resource server. The token's `aud` is **reported** in the\nresponse and takes part in no decision: this service issues several audiences (`ankasecure-admin` for human users,\n`ankasecure-core` for application actors), and all of them introspect identically.\n\n**Both token planes are searched.** The value may be a JWT access token or an opaque\nrefresh token. Per RFC 7662 §2.1 the optional `token_type_hint` only **reorders** the\nsearch — it never ends it, so a wrong hint still finds the token. An unrecognized or\nempty hint is treated as absent and is never an error.\n\n**Request format** — `application/x-www-form-urlencoded`, as RFC 7662 §2.1 specifies.\nA JSON body is answered `400` carrying the RFC 6749 §5.2 error object, not `415` — see\nthe 400 response below.\n\n**Authorization** — requires `platform.token.read` (every tenant, for a caller of the\nplatform tenant on a PRIVATE_CLOUD or ON_PREMISE deployment) or `auth.tenant.token.read`\n(the caller's own tenant, in every deployment mode). A caller holding both reaches what\neither covers. The tenant of the introspected token is the one the token itself, or its\nrefresh record, names; a token naming no tenant is reported inactive.\n\n**Disclosure** — an inactive token yields `{\"active\": false}` and nothing else: no\nclaims, and no distinction between expired, revoked, belonging to another tenant, and\nnever having existed. A token belonging to another tenant is reported inactive rather\nthan refused, because a `403` would confirm the token exists while declining to\ndescribe it.\n","operationId":"introspectToken","requestBody":{"description":"Form-encoded introspection request (RFC 7662 §2.1)","content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/IntrospectRequest"},"examples":{"1. Access token, no hint":{"summary":"Both planes searched, JWT first","description":"1. Access token, no hint","value":"token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3YzllNjY3OSJ9.sig"},"2. Refresh token with a matching hint":{"summary":"The hint puts the refresh plane first","description":"2. Refresh token with a matching hint","value":"token=Zm9vYmFyYmF6cXV4MTIzNDU2&token_type_hint=refresh_token"},"3. Refresh token with the WRONG hint":{"summary":"Still found - the hint reorders the search, it never ends it","description":"3. Refresh token with the WRONG hint","value":"token=Zm9vYmFyYmF6cXV4MTIzNDU2&token_type_hint=access_token"}}}},"required":true},"responses":{"400":{"description":"The token parameter is absent or blank, the body was not application/x-www-form-urlencoded, OR a credential parameter (token, client_secret, refresh_token, subject_token, client_assertion, invitation_token or another credential) was sent in the query string, which is refused before the caller is authenticated or the token examined. Carries the RFC 6749 §5.2 error object (`{\"error\": \"invalid_request\", ...}`), NOT problem+json — RFC 7009 §2.2.1 and RFC 7662 §2.3 both specify that shape for these endpoints, and this endpoint answers every cause in it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuth2ErrorResponse"}}}},"401":{"description":"No access token was presented, or the one presented is not accepted. A person's token is challenged with `WWW-Authenticate: Bearer`. An administrative workload's token is accepted only under `Authorization: DPoP` with a proof bound to the token and to this request (RFC 9449); otherwise the challenge is `WWW-Authenticate: DPoP error=\"invalid_token\"` (the token was presented under `Bearer`, or is not bound to a key) or `WWW-Authenticate: DPoP error=\"invalid_dpop_proof\"` (the proof is missing, malformed, already used, or names another method or URL). The body is the same in every case.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Token missing, rejected, or a workload token without a valid DPoP proof":{"description":"1. Token missing, rejected, or a workload token without a valid DPoP proof","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication is required to call this endpoint.","instance":"/api/v3/auth/token/s/3f2b8c1e-9d4a-4e7b-a5c6-1b2d3e4f5a6b","timestamp":1765057500,"extensions":{"code":"UNAUTHENTICATED"}}}}}}},"403":{"description":"The caller is authenticated but holds neither read scope, or holds only platform.token.read on a SaaS deployment or from outside the platform tenant. An administrative workload outside the platform tenant that holds a `platform.token.*` scope is refused, whatever the operation; a tenant workload acts with `auth.tenant.token.*` in the tenant its token names. Decided from the caller's token, its scopes and the deployment mode, before the submitted token is examined.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limit exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready` with `Retry-After`: the store that records each DPoP proof once could not decide whether this proof was already used, so the workload's request was neither admitted nor refused as a bad credential, and nothing changed.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"5"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The DPoP proof could not be checked for replay":{"description":"1. The DPoP proof could not be checked for replay","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/token/introspect"}}}}}},"200":{"description":"Token introspection response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntrospectResponse"},"examples":{"Active Token":{"summary":"Valid active token","description":"Active Token","value":{"active":true,"scopes":["admin.tenant.user.create","admin.tenant.user.read"],"username":"admin@acmecorp.com","token_type":"access_token","exp":1737666000,"iat":1737630000,"sub":"7c9e6679-7425-40de-944b-e07fc1f90ae7","aud":"ankasecure-admin","iss":"https://auth.example.com"}},"Inactive Token":{"summary":"Expired or revoked token","description":"Inactive Token","value":{"active":false}}}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/tenant/users/{userId}/sessions/revoke":{"post":{"tags":["Tenant User Sessions"],"summary":"End all of a member's sessions in your tenant","description":"Ends every session of a member that is currently in the tenant of the caller's access token,\nincluding one the list does not show. Every access token the member was issued for this tenant\nbefore the request is refused, and every refresh or tenant switch of those sessions is refused\nwith <code>invalid_grant</code>. A session of the member that is currently in another tenant\nkeeps working, and cannot switch into this tenant afterwards. The member can sign in to the\ntenant again while the membership is active.\n\nThe revocation is recorded as <code>auth.session.revoked-tenant</code> for the tenant, naming\nthe administrator as the actor and the member as the target, before the answer.\n","operationId":"revokeTenantUserSessions","parameters":[{"name":"userId","in":"path","description":"The member's identity identifier: the user id the tenant's user list shows.","required":true,"schema":{"type":"string","format":"uuid"},"example":"7c9e6679-7425-40de-944b-e07fc1f90ae7"}],"responses":{"204":{"description":"Every session of the member in the tenant was ended. Also 204 when there was none."},"400":{"description":"`userId` is not a UUID.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"No access token was presented, or the one presented is not accepted. A person's token is challenged with `WWW-Authenticate: Bearer`. An administrative workload's token is accepted only under `Authorization: DPoP` with a proof bound to the token and to this request (RFC 9449); otherwise the challenge is `WWW-Authenticate: DPoP error=\"invalid_token\"` (the token was presented under `Bearer`, or is not bound to a key) or `WWW-Authenticate: DPoP error=\"invalid_dpop_proof\"` (the proof is missing, malformed, already used, or names another method or URL). The body is the same in every case.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"The access token does not carry `auth.tenant.token.revoke`. An administrative workload outside the platform tenant that holds a `platform.token.*` scope is refused, whatever the operation; a tenant workload acts with `auth.tenant.token.*` in the tenant its token names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Missing scope":{"description":"1. Missing scope","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"You do not have permission to access this resource.","instance":"/api/v3/auth/tenant/users/7c9e6679-7425-40de-944b-e07fc1f90ae7/sessions","timestamp":1765057500}}}}}},"404":{"description":"The identity holds no active membership of the caller's tenant. Nothing changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Not a member of this tenant":{"description":"1. Not a member of this tenant","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"No member with this identifier in this tenant","instance":"/api/v3/auth/tenant/users/7c9e6679-7425-40de-944b-e07fc1f90ae7/sessions","timestamp":1765057500}}}}}},"502":{"description":"The account service that confirms the membership could not be reached, or answered outside its contract. Nothing changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready`: the session store could not be read or written, and nothing changed; or, for a revocation, its audit record could not be confirmed after the sessions were ended, which stands; or an administrative workload's DPoP proof could not be checked for replay, and nothing was read or changed. Retry after `Retry-After`.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"5"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Session store unavailable":{"description":"1. Session store unavailable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/sessions/revoke-others"}}}}}},"504":{"description":"The account service that confirms the membership did not answer within the deadline. Nothing changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/platform/identities/{identityId}/sessions/revoke":{"post":{"tags":["Platform Identity Sessions"],"summary":"End all of an identity's sessions","description":"Ends every session of an identity in every tenant, including one the list does not show: every\naccess token and refresh token the identity held before the request is refused from the answer\non. The identity can sign in again afterwards.\n\nThe caller must be signed in to the platform tenant: any other caller is refused with\n<code>403</code> before anything is read. Not available on a SaaS deployment.\n\nThe revocation is recorded as <code>auth.session.revoked-by-administrator</code>, naming the\nadministrator as the actor and the identity as the target, before the answer.\n","operationId":"revokePlatformIdentitySessions","parameters":[{"name":"identityId","in":"path","description":"The identity's identifier: the subject (`sub`) of the person's access tokens.","required":true,"schema":{"type":"string","format":"uuid"},"example":"7c9e6679-7425-40de-944b-e07fc1f90ae7"}],"responses":{"204":{"description":"Every session of the identity was ended. Also 204 when there was none."},"400":{"description":"`identityId` is not a UUID.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"No access token was presented, or the one presented is not accepted. A person's token is challenged with `WWW-Authenticate: Bearer`. An administrative workload's token is accepted only under `Authorization: DPoP` with a proof bound to the token and to this request (RFC 9449); otherwise the challenge is `WWW-Authenticate: DPoP error=\"invalid_token\"` (the token was presented under `Bearer`, or is not bound to a key) or `WWW-Authenticate: DPoP error=\"invalid_dpop_proof\"` (the proof is missing, malformed, already used, or names another method or URL). The body is the same in every case.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"`forbidden`: the access token does not carry the scope, or was not issued for the platform tenant, in which case nothing is read; or the deployment is SaaS. An administrative workload outside the platform tenant that holds a `platform.token.*` scope is refused, whatever the operation; a tenant workload acts with `auth.tenant.token.*` in the tenant its token names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Not the platform tenant, or missing scope":{"description":"1. Not the platform tenant, or missing scope","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"You do not have permission to access this resource.","instance":"/api/v3/auth/tenant/users/7c9e6679-7425-40de-944b-e07fc1f90ae7/sessions","timestamp":1765057500}},"2. SaaS deployment":{"description":"2. SaaS deployment","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Deployment Mode Restriction","status":403,"detail":"This operation is only available in PRIVATE_CLOUD or ON_PREMISE deployments.","instance":"/api/v3/auth/platform/identities/7c9e6679-7425-40de-944b-e07fc1f90ae7/sessions","timestamp":1765057500}}}}}},"503":{"description":"`/problems/dependency-not-ready`: the session store could not be read or written, and nothing changed; or, for a revocation, its audit record could not be confirmed after the sessions were ended, which stands; or an administrative workload's DPoP proof could not be checked for replay, and nothing was read or changed. Retry after `Retry-After`.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"5"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Session store unavailable":{"description":"1. Session store unavailable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/sessions/revoke-others"}}}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/authorize-challenge":{"post":{"tags":["Authorization Challenge"],"summary":"Take one step of a sign-in","description":"Takes one step of the console's sign-in, the Authorization Challenge Endpoint of\n<a href=\"https://datatracker.ietf.org/doc/html/draft-ietf-oauth-first-party-apps-04\">OAuth 2.0\nfor First-Party Applications</a>. Each answer that continues the sign-in is\n<code>403</code> with <code>error: insufficient_authorization</code>, a new\n<code>auth_session</code> and the step expected <code>next</code>; the last answer is\n<code>200</code> with an <code>authorization_code</code>.\n\n**1. Start.** Send <code>code_challenge</code> (BASE64URL(SHA-256(code_verifier)), RFC 7636) and\n<code>code_challenge_method=S256</code>, nothing else. The answer is <code>next: credential</code>\nwith the first-credential <code>methods</code> (<code>passkey</code>, <code>password</code>,\n<code>ldap-password</code>, <code>recovery-code</code>) and the WebAuthn request options of a\npasskey assertion in <code>public_key</code>: the RP ID is the host of the Management hostname origin,\n<code>userVerification</code> is <code>required</code> and <code>allowCredentials</code> is empty,\nso the answer is the same for every caller and names no account. <code>plain</code>, or no method,\nis <code>invalid_request</code>.\n\nAn onboarding start adds one start parameter, never two:\n\n- <code>activation_token</code>, the token of an activation link, or <code>enrolment_token</code>, the\n  single-use token an assisted recovery handed over: the answer is\n  <code>next: enrolment-required</code> with the enrolments the person's tenant accepts. No other\n  credential is asked; a code is issued only once a new authenticator is enrolled, which turns the\n  activated membership active, or redeems the enrolment token. A token already used, expired or\n  unknown is the refusal.\n- <code>invitation_token</code>, the token of an invitation to a further tenant: the answer is the\n  one above. The person signs in with the authenticators they hold; when they satisfy the inviting\n  tenant's policy the invitation is accepted with the code, otherwise the answer is\n  <code>next: enrolment-required</code> for an additional factor, and the membership turns active\n  only once it is enrolled. An identity with no active membership signs in only with an invitation.\n- <code>claim_code</code> with <code>username</code>: the claim of a new deployment that no administrator\n  holds yet, with the single-use code the operator of the environment host minted (valid one hour). The\n  claim creates the deployment's first platform administrator, named by <code>username</code>, and the\n  answer is <code>next: enrolment-required</code>: choose a password with\n  <code>action=password-set</code> and enrol an authenticator app, or enrol a passkey where the answer\n  offers one. A code that is absent, wrong, expired or already used, and any code once the deployment\n  is claimed, is <code>400 instance-claim-code-invalid</code> and creates nothing: the answer does not\n  say whether the deployment is claimed. Starting again with the same code and address\n  while the claim's enrolment is not complete continues the same claim: no second administrator is\n  created. The code is never echoed, logged or stored by this service.\n\n**Emergency access.** <code>purpose=break_glass</code>, with the PKCE challenge and no token, starts\nthe emergency-access entry of a designated break-glass member. The answer is\n<code>next: second-factor</code> offering only a passkey assertion, with request options that serve\nthis entry alone; it is the same for every caller and names no account. Only a security key or a\ndevice-bound passkey of the identity's designated membership admits, while the identity holds the\ndeployment's minimum of them; a password, a TOTP code, a recovery code, a synced passkey or a federated\nproof is the refusal, as is a member not designated. The code opens a break-glass session: its access\ntokens carry <code>break_glass: true</code> and the membership's own roles, it is never remembered,\ncannot switch tenant, and its token is never exchanged for another tenant. Every attempt is recorded,\nand an admitted one is issued even while the audit record is unavailable.\n\n**2. First credential.** Send the <code>auth_session</code> and one of:\n\n- <code>action=passkey-assertion</code> with the members of the WebAuthn assertion\n  (<code>credential_id</code>, <code>client_data_json</code>, <code>authenticator_data</code>,\n  <code>signature</code>, <code>user_handle</code>), made with the request options of the previous\n  answer: their challenge is bound to its <code>auth_session</code>.\n- <code>action=password</code> with <code>username</code> (the sign-in address) and\n  <code>password</code>.\n- <code>action=ldap-password</code> with <code>tenant_id</code>, <code>username</code> (the\n  directory username) and <code>password</code> (the directory password), verified by that\n  tenant's LDAP directory.\n- <code>action=recovery-code</code> with <code>username</code> and <code>recovery_code</code>.\n\nA password, local or directory, never ends the sign-in on its own, and no tenant is listed after\nit: the answer is <code>next: second-factor</code> when the person holds a factor the tenant's\npolicy admits (<code>methods: [\"totp\"]</code>, or <code>[\"passkey\"]</code> with\n<code>public_key</code>), or <code>next: enrolment-required</code> naming the\n<code>paths</code> to enrol when they hold none. A recovery code never yields a code either: the\nanswer is <code>next: recovery</code>, where only the enrolment of a new authenticator is\nadmitted.\n\n**3. Second factor.** After a password, send <code>action=totp</code> with <code>otp</code>, the\ncode the authenticator app shows, only when the previous answer offered <code>totp</code>; a code\nsent where the tenant allows only a passkey is refused. Or send the passkey assertion the answer\nasked for.\n\n**4. Restricted states.** Three answers admit only their own actions; any other action is the\nrefusal:\n\n- <code>next: setup</code>: a password another person set (an initial administrator account). Prove\n  a factor already held when the answer asks for it, enrol one, and then choose your own password\n  with <code>action=password-set</code> where the account keeps one. A platform administrator's code\n  answer carries <code>recoverability_recommended: true</code>: a second platform administrator and a\n  break-glass account are recommended.\n- <code>next: enrolment-required</code>: an activation, an invitation that needs an additional\n  factor, or an assisted recovery. Enrol a passkey, or, where <code>paths</code> offers\n  <code>totp-enrolment</code>, choose a password with <code>action=password-set</code> and enrol an\n  authenticator app. A password alone never yields a code.\n- <code>next: recovery</code>: a recovery code was accepted. Only the enrolment of a new authenticator\n  is admitted. Once it is enrolled the code is issued, the recovery code is consumed and every other\n  session of the person is ended.\n\nEnrolment actions, each admitted only where the previous answer named its method:\n\n- <code>action=passkey-registration-options</code> (no member): the answer carries\n  <code>public_key_creation</code>, the WebAuthn creation options for\n  <code>navigator.credentials.create()</code>, bound to the person and to the new\n  <code>auth_session</code>. Then <code>action=passkey-registration</code> with\n  <code>credential_id</code>, <code>client_data_json</code>, <code>attestation_object</code>,\n  <code>transports</code> (one parameter per hint), <code>cred_props_rk</code> and\n  <code>display_name</code>.\n- <code>action=totp-enrolment</code> (no member, after a password the person proved or chose): the\n  answer carries <code>totp</code>, the app's secret, shown once. Then\n  <code>action=totp-enrolment-verification</code> with <code>otp</code>, the app's first code.\n- <code>action=password-set</code> with <code>password</code>. The password policy decides it: 15 to\n  128 characters, no composition rule, no common or compromised value, not containing the local part\n  of the e-mail address. A password the policy refuses is\n  <code>400 password-policy-violation</code> naming the rule in <code>extensions.rule</code>, and the\n  sign-in continues with the <code>auth_session</code> in <code>extensions.auth_session</code>.\n\nThe answer that follows the enrolment of a person's first passkey or authenticator app carries their\nten <code>recovery_codes</code>, shown once. A sign-in started from an activation or recovery link\nreceives them on the answer that completes it (the authorization code or the tenant selection), never\non an earlier one, and they replace any codes the person held before. An enrolment never verified, a registration that does not\nverify, or a first code that is wrong is the refusal.\n\n**5. Tenant.** When the person may enter several tenants, the answer is\n<code>next: tenant-selection</code> listing them; send <code>action=select-tenant</code> with one\n<code>tenant_id</code>. A listed tenant whose policy the proofs already satisfy needs no new\ncredential. A tenant whose policy asks for more answers <code>next: second-factor</code> or\n<code>next: enrolment-required</code> with the <code>paths</code> that would admit, each naming\nthe capability properties its authenticator must hold. The selected tenant decides with its whole\nauthentication posture: a tenant that admits only its identity provider, or a member restricted to it,\noffers only the federated path; a tenant that admits only local authentication offers no federated\npath. An invitation is decided the same way for the inviting tenant.\n\n**6. Federated path.** Once the proofs verify the person, a membership entered through an identity\nprovider is offered as a path <code>{\"type\": \"federated\", \"idp_id\": …, \"display_name\": …}</code>\nwith the method <code>federated</code>. One valid path the proofs do not satisfy is offered alone;\nseveral are listed for the person to choose. Send <code>action=federated</code> with that path's\n<code>idp_id</code>; a provider the answer did not offer is the refusal. The answer is\n<code>403</code> with <code>error: redirect_to_web</code>, a single-use <code>request_uri</code> and\nits <code>expires_in</code> (at most 60 seconds); the <code>auth_session</code> is spent. Navigate the\nbrowser (top level) to <code>GET /api/v3/auth/idp/initiate?request_uri=…</code> before it expires: the\nperson signs in at the provider, and the provider's callback continues this sign-in with the same\nPKCE challenge. When the identity the provider asserts is this sign-in's and the tenant admits it, the\nbrowser is sent to the console's federation callback with the authorization code in the URL fragment\n(<code>#code=…</code>, never the query); redeem it as in step 7. Any other outcome is sent to the\nconsole's sign-in page with an error, and the sign-in ends.\n\n**Second factor after an identity provider.** An administrator of a tenant (a membership whose roles hold a write scope) entering through an identity provider is\nadmitted on the provider's proof alone only when the provider is trusted for multi-factor\nauthentication and its assertion proves it: the OIDC <code>amr</code> or the SAML\n<code>AuthnContextClassRef</code> of the validated token or assertion, with the instant the provider\nstates. Otherwise the sign-in is not refused: the browser is sent to the console's sign-in page with an\n<code>auth_session</code> in the fragment (<code>#auth_session=…</code>, never the query). Send that\n<code>auth_session</code> with no <code>action</code>; a sign-in that started at the identity provider\nrather than here sends it with a new <code>code_challenge</code> and\n<code>code_challenge_method=S256</code>, and one that already has a challenge sends none (a second one is\n<code>400 invalid_session</code>). The answer is <code>next: second-factor</code> with a passkey or a TOTP\ncode, the TOTP code completing the provider's proof as it completes a password, or\n<code>next: enrolment-required</code> with the enrolment of either. Nothing else is admitted, and the\ncode it ends in is redeemed as in step 7; its <code>amr</code> carries the provider's methods and the\nlocal one. Members, the platform tenant and emergency access are decided as before.\n\n**7. Code.** Redeem the <code>authorization_code</code> once, within 60 seconds, at\n<code>POST /api/v3/auth/token</code> with <code>grant_type=authorization_code</code>, the\n<code>code</code> and the <code>code_verifier</code>. The session's <code>amr</code> names the\nproofs: <code>user</code> with <code>hwk</code> or <code>swk</code> for a passkey;\n<code>pwd</code>, <code>otp</code> and <code>mfa</code> for a password and a TOTP code; the methods\nthe identity provider reported for a federated path. A passkey or an authenticator app enrolled in\nthe sign-in counts as the proof it is. The session is authenticated as the authority that admitted\nthe tenant: the person's own authenticators, the directory, or the identity provider of the\nfederated path.\n\n**Tenant switch.** A switch into a tenant whose authentication posture the session's evidence does\nnot satisfy is answered at the token endpoint with <code>403 insufficient_authorization</code> and an\n<code>auth_session</code> bound to that session and that tenant. Its first request here carries that\n<code>auth_session</code>, a new <code>code_challenge</code> with\n<code>code_challenge_method=S256</code>, and no <code>action</code>; the answer is the step-up the\ntenant requires: a passkey assertion, a TOTP code after the password the session was signed in\nwith, or the federated path of the tenant's identity provider. Only proofs of the session's own\nidentity are accepted, and nothing is enrolled. The code it ends in is redeemed at the token\nendpoint with the session's refresh token, and the same session continues in that tenant.\n\n**Fresh reauthentication.** A refresh carrying <code>max_age</code> whose session's most recent\nauthentication for its tenant is older is answered at the token endpoint with\n<code>403 insufficient_authorization</code> and an <code>auth_session</code> bound to that session and\nits tenant. It continues here exactly as a tenant switch does, and re-proves the authority that admits\nthe tenant: a passkey assertion; a password (<code>action=password</code>, the session's own sign-in\naddress) and then a TOTP code, where the session was admitted with them; or the federated path of the\ntenant's identity provider. It is decided on these new proofs only, never on the session's earlier\nones. The code it ends in is redeemed at the token endpoint with the session's refresh token: the same\nsession continues, and its tokens carry a new <code>reauth_time</code>.\n\n**The auth_session.** Every answer hands out a new value and the one presented is no longer\naccepted. A sign-in lasts at most 10 minutes from its start and is one transaction: an unknown,\nalready answered or expired value, or a second <code>code_challenge</code> on a value, is\n<code>400 invalid_session</code>, and the client starts again.\n\n**One refusal.** Every refused step — an assertion that does not verify, a passkey that is not\nregistered, a wrong password, TOTP code or recovery code, a failed directory bind, a locked\naccount, a step the previous answer did not offer, a tenant outside the list, an identity that may\nnot enter, a start token already used — is the same <code>400 invalid_grant</code> a wrong password receives at the token\nendpoint, and ends the sign-in. Every refused credential counts toward the sign-in rate limit of the\nclient address and toward the lockout of its own authenticator: a password or TOTP code for the client\nnetwork it came from, a recovery code or a passkey for the account. A lock of one authenticator never\nrefuses another, so wrong passwords never lock out a passkey or an organisation's identity provider.\n\n**Origin.** Only requests whose <code>Origin</code> is one of the Management addresses the console is\npublished on are accepted; any other, or none, is <code>403 forbidden-origin</code> before anything is\nread or stored. A sign-in belongs to the address it started on: a later request of it from another\nManagement address is the same <code>403</code>, and the sign-in is left as it was.\n\n**Passkeys and the console's address.** A passkey belongs to the deployment's Management host name. A\nnext-step answer states <code>passkey_available</code>: <code>true</code> where a passkey can be used,\n<code>false</code> with <code>passkey_unavailable_reason</code> on another Management address (such as\nthe host's IP address). There no passkey method, path or request options are offered, the sign-in\ncontinues with a password and an authenticator app, and a passkey ceremony asked for anyway is\n<code>409 passkey-unavailable-on-origin</code>. The client shows this statement as it is answered.\n\n**Unavailable dependency.** When a step cannot be taken because a dependency is unavailable, the\nanswer is <code>503</code> with <code>Retry-After</code>: nothing is issued and no step is skipped;\nthe sign-in starts again.\n\nRequest body: <code>application/x-www-form-urlencoded</code> (draft §5.1). Every answer is\n<code>Cache-Control: no-store</code>.\n","operationId":"submitAuthorizationChallenge","requestBody":{"description":"One step of the sign-in","content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/AuthorizationChallengeRequest"},"examples":{"1. Start a sign-in (PKCE S256)":{"description":"1. Start a sign-in (PKCE S256)","value":"code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256"},"1b. Start an activation from its link":{"description":"1b. Start an activation from its link","value":"code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&activation_token=eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI3YzFlNGIyYSJ9.c2lnbmF0dXJl"},"1c. Start an assisted recovery's enrolment":{"description":"1c. Start an assisted recovery's enrolment","value":"code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&enrolment_token=q7Vw2ZbN0xR4tY8uI1oP5aS9dF3gH6jK2lZ7xC0vB4n"},"1d. Continue a tenant switch's step-up":{"description":"1d. Continue a tenant switch's step-up","value":"auth_session=jT4Wq7Zp2Lk9Rv1Xc6Bn3Md8Fs5Gh0Yu2Ei7Oa4Tw9&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256"},"1e. Start an emergency access (break-glass)":{"description":"1e. Start an emergency access (break-glass)","value":"code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&purpose=break_glass"},"1f. Claim the instance (Quick Start)":{"description":"1f. Claim the instance (Quick Start)","value":"code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&claim_code=Jt5cX2mQ8vR1nY7bK4wL9pZ0sD3fH6gA2eU5iO8rT1y&username=platform.owner%40acme.example"},"2. Passkey assertion":{"description":"2. Passkey assertion","value":"action=passkey-assertion&auth_session=cX7Jb2kYw9TqL4mVn8RzP1sD6fGhA3eK0uWoZ5iBtCy&credential_id=AV3kUf7zq1yJm0pXr9tB2cQe&client_data_json=eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoicTRabDh3MFhRbTF2WXAya1I3dE4zc0JmSGNKNnVEOWVBNWdMeFZvV2lFMCIsIm9yaWdpbiI6Imh0dHBzOi8vZGV2LmFua2F0ZWNoLmNvIn0&authenticator_data=dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvAdAAAABw&signature=MEUCIQDkKz3r9ZlB2pXyQ0mVt8sN4cJ7aF1eH6gD5uW2oL9iRgIgT3vY8bK1nM0qP7xZ4cS6dA2fE9hJ5lU8wR3tG1yB0oI&user_handle=Xw3k9VhQ0mT2Lr5pYc8aN1sJ7dFb4GzKe6uHxWqRo0y"},"3. Password":{"description":"3. Password","value":"action=password&auth_session=cX7Jb2kYw9TqL4mVn8RzP1sD6fGhA3eK0uWoZ5iBtCy&username=jane.doe%40acme.example&password=harbour+lantern+quietly+folding"},"4. Directory password (LDAP)":{"description":"4. Directory password (LDAP)","value":"action=ldap-password&auth_session=cX7Jb2kYw9TqL4mVn8RzP1sD6fGhA3eK0uWoZ5iBtCy&tenant_id=00000000-0000-0000-0000-000000000003&username=jdoe&password=harbour+lantern+quietly+folding"},"5. TOTP code after a password":{"description":"5. TOTP code after a password","value":"action=totp&auth_session=Pk4Wn7Rb2Xc9Vm1Ld6Ty3Qs8Gh5Fa0Ej2Uo7Ti4Rk9Zw&otp=492039"},"6. Recovery code":{"description":"6. Recovery code","value":"action=recovery-code&auth_session=cX7Jb2kYw9TqL4mVn8RzP1sD6fGhA3eK0uWoZ5iBtCy&username=jane.doe%40acme.example&recovery_code=7K3M-Q9TD-2XWH-B8NR-5FCJ-ZP4V"},"7. Select a tenant":{"description":"7. Select a tenant","value":"action=select-tenant&auth_session=Hq2Vd8nKx5RtB0mYc7LwZ3pJf9sG1aE4uO6iTkN2rQe&tenant_id=00000000-0000-0000-0000-000000000003"},"8. Ask for the creation options of a new passkey":{"description":"8. Ask for the creation options of a new passkey","value":"action=passkey-registration-options&auth_session=Zt5Lm1qWc8Ny3Kd0Hr6Vb2Xp9Gs4Fa7Ej1Uo3Ti8Rk0Ab"},"9. Register the new passkey":{"description":"9. Register the new passkey","value":"action=passkey-registration&auth_session=Wq3Ne7Ry1Tb5Ux9Ic2Od6Pf0Ag4Sh8Jk2Ll6Zm0Xn4Cb&credential_id=AV3kUf7zq1yJm0pXr9tB2cQe&client_data_json=eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0&attestation_object=o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBdAAAAAA&transports=internal&transports=hybrid&cred_props_rk=true&display_name=Work+laptop"},"10. Start the enrolment of an authenticator app":{"description":"10. Start the enrolment of an authenticator app","value":"action=totp-enrolment&auth_session=Ky6Pw0Mn4Bv8Cx2Zl5Jh9Gf3Ds7Aq1We5Rt9Yu3Io7Pa"},"11. Complete it with the app's first code":{"description":"11. Complete it with the app's first code","value":"action=totp-enrolment-verification&auth_session=Mx2Bn6Vc0Zl4Kj8Hg1Fd5Sa9Qw3Er7Ty1Ui5Op9As3Df&otp=492039"},"12. Choose your own password":{"description":"12. Choose your own password","value":"action=password-set&auth_session=Nb7Vc1Xz5Lk9Jh3Gf6Ds0Aq4We8Rt2Yu6Io0Pa4Sd8Fg&password=harbour+lantern+quietly+folding"},"13. Take the federated path of the tenant's identity provider":{"description":"13. Take the federated path of the tenant's identity provider","value":"action=federated&auth_session=Kd3Fg7Hj1Kl5Zx9Cv3Bn7Mq1We5Rt9Yu3Io7Pa1Sd5Fg&idp_id=3f1c2a9e-4b7d-4e2a-9c11-0a2b3c4d5e6f"}}}},"required":true},"responses":{"200":{"description":"The sign-in is complete: the authorization code to redeem at the token endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizationChallengeResponse"},"examples":{"1. Authorization code after a passkey":{"description":"1. Authorization code after a passkey","value":{"authorization_code":"uM3rQ9xT2kW7vB5nY1cL8hJ4gF6dS0aZ_pE3oN7iR2k"}},"2. Code after an activation: the first passkey with its recovery codes":{"description":"2. Code after an activation: the first passkey with its recovery codes","value":{"authorization_code":"uM3rQ9xT2kW7vB5nY1cL8hJ4gF6dS0aZ_pE3oN7iR2k","recovery_codes":["7K3M-Q9TD-2XWH-B8NR-5FCJ-ZP4V","3HQW-8NVD-K2ZX-P7MR-4BTC-L9FJ"]}},"3. Code that completes a platform administrator's setup":{"description":"3. Code that completes a platform administrator's setup","value":{"authorization_code":"uM3rQ9xT2kW7vB5nY1cL8hJ4gF6dS0aZ_pE3oN7iR2k","recovery_codes":["7K3M-Q9TD-2XWH-B8NR-5FCJ-ZP4V","3HQW-8NVD-K2ZX-P7MR-4BTC-L9FJ"],"recoverability_recommended":true}}}}}},"400":{"description":"A refused request (RFC 6749 §5.2 error format):\n\n- `invalid_request`: a malformed request — no `code_challenge` or a method other than S256 on the first request, two start parameters, an unknown `action`, a missing or malformed member of the action. Nothing was consumed.\n- `invalid_session`: the `auth_session` is unknown, already answered, expired, or was presented with a second `code_challenge` (a tenant switch's step-up admits one, on its first request only). Start again.\n- `invalid_grant`: the step was refused. The answer is the one a wrong password receives at the token endpoint, whatever the cause, and the sign-in ends.\n- `password-policy-violation` (application/problem+json): the password of password-set breaks the password policy, named in `extensions.rule`. Nothing was accepted; the sign-in continues with `extensions.auth_session`.\n- `instance-claim-code-invalid` (application/problem+json): the claim code of a claim start is absent, wrong, expired or already used. Nothing was created; the sign-in did not start.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuth2ErrorResponse"},"examples":{"1. invalid_request: plain PKCE method":{"description":"1. invalid_request: plain PKCE method","value":{"error":"invalid_request","error_description":"code_challenge_method must be S256","error_uri":"https://docs.ankatech.co/errors/invalid_request","type":"https://docs.ankatech.co/errors/invalid_request","title":"Invalid Request","status":400,"detail":"code_challenge_method must be S256","instance":"/api/v3/auth/authorize-challenge","timestamp":1790503200}},"2. invalid_session: an auth_session already answered":{"description":"2. invalid_session: an auth_session already answered","value":{"error":"invalid_session","error_description":"The authentication session is unknown, has expired or was already used. Start again.","error_uri":"https://docs.ankatech.co/errors/invalid_session","type":"https://docs.ankatech.co/errors/invalid_session","title":"Invalid Session","status":400,"detail":"The authentication session is unknown, has expired or was already used. Start again.","instance":"/api/v3/auth/authorize-challenge","timestamp":1790503200}},"4. password-policy-violation: a chosen password that is too short":{"description":"4. password-policy-violation: a chosen password that is too short","value":{"type":"https://docs.ankatech.co/errors/password-policy-violation","title":"Password Policy Violation","status":400,"detail":"The password must be at least 15 characters long.","instance":"/api/v3/auth/authorize-challenge","timestamp":1790503200,"extensions":{"rule":"too-short","auth_session":"Lp4Kc8Vn2Xb6Mz0Qa3Ws7Ed1Rf5Tg9Yh3Uj7Ik1Ol5Pz"}}},"3. invalid_grant: a refused passkey assertion":{"description":"3. invalid_grant: a refused passkey assertion","value":{"error":"invalid_grant","error_description":"Invalid email or password","error_uri":"https://docs.ankatech.co/errors/invalid_grant","type":"https://docs.ankatech.co/errors/invalid_grant","title":"Invalid Grant","status":400,"detail":"Invalid email or password","instance":"/api/v3/auth/authorize-challenge","timestamp":1790503200}},"5. instance-claim-code-invalid: a claim code that is wrong or expired":{"description":"5. instance-claim-code-invalid: a claim code that is wrong or expired","value":{"type":"https://docs.ankatech.co/errors/instance-claim-code-invalid","title":"Claim Code Not Accepted","status":400,"detail":"The claim code is not valid. Ask the operator of the environment host for a new one.","instance":"/api/v3/auth/authorize-challenge"}}}}}},"409":{"description":"application/problem+json:\n\n- `passkey-unavailable-on-origin`: a passkey ceremony asked for on a Management address that is not the deployment's host name (such as the host's IP address), where no passkey can be used. No `Retry-After`: the answer is the same until the console is opened on the host name. Nothing was issued. The same answer ends a sign-in, an activation, a recovery enrolment or an invitation whose remaining requirement only a passkey can meet, when it runs on such an address: no credential was refused, and the same sign-in started on the host name continues.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. An activation on the host's IP address that only a passkey can complete":{"description":"1. An activation on the host's IP address that only a passkey can complete","value":{"type":"https://docs.ankatech.co/errors/passkey-unavailable-on-origin","title":"Passkey Unavailable On This Address","status":409,"detail":"Passkeys cannot be used on this address of the deployment. Open the console on the deployment's host name to use a passkey, or sign in with a password and an authenticator app.","instance":"/api/v3/auth/authorize-challenge"}},"2. A passkey asked for on the host's IP address":{"description":"2. A passkey asked for on the host's IP address","value":{"type":"https://docs.ankatech.co/errors/passkey-unavailable-on-origin","title":"Passkey Unavailable On This Address","status":409,"detail":"Passkeys cannot be used on this address of the deployment. Open the console on the deployment's host name to use a passkey, or sign in with a password and an authenticator app.","instance":"/api/v3/auth/authorize-challenge"}}}}}},"403":{"description":"Either the sign-in continues with another step (`insufficient_authorization`, application/json), the sign-in continues in the browser through the identity provider of the federated path taken (`redirect_to_web` with `request_uri` and `expires_in`, application/json), or the request's `Origin` is not one of the Management addresses the console is published on, or is another Management address than the one the sign-in started on (`forbidden-origin`, application/problem+json; nothing was read, stored or consumed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizationChallengeResponse"},"examples":{"1. Start: a passkey is asked for (the same for every caller)":{"description":"1. Start: a passkey is asked for (the same for every caller)","value":{"error":"insufficient_authorization","auth_session":"cX7Jb2kYw9TqL4mVn8RzP1sD6fGhA3eK0uWoZ5iBtCy","next":"credential","methods":["passkey","password","ldap-password","recovery-code"],"public_key":{"challenge":"q4Zl8w0XQm1vYp2kR7tN3sBfHcJ6uD9eA5gLxVoWiE0","rpId":"dev.ankatech.co","timeout":300000,"userVerification":"required","allowCredentials":[]},"passkey_available":true}},"1b. Start on the host's IP address: no passkey is offered":{"description":"1b. Start on the host's IP address: no passkey is offered","value":{"error":"insufficient_authorization","auth_session":"cX7Jb2kYw9TqL4mVn8RzP1sD6fGhA3eK0uWoZ5iBtCy","next":"credential","methods":["password","ldap-password","recovery-code"],"passkey_available":false,"passkey_unavailable_reason":"origin-not-relying-party"}},"2. Several tenants open to the person":{"description":"2. Several tenants open to the person","value":{"error":"insufficient_authorization","auth_session":"Hq2Vd8nKx5RtB0mYc7LwZ3pJf9sG1aE4uO6iTkN2rQe","next":"tenant-selection","methods":["select-tenant"],"tenants":[{"tenant_id":"00000000-0000-0000-0000-000000000003","tenant_name":"Acme Corporation"},{"tenant_id":"00000000-0000-0000-0000-000000000004","tenant_name":"Beta Logistics"}]}},"3. A TOTP code is asked for after a password":{"description":"3. A TOTP code is asked for after a password","value":{"error":"insufficient_authorization","auth_session":"Pk4Wn7Rb2Xc9Vm1Ld6Ty3Qs8Gh5Fa0Ej2Uo7Ti4Rk9Zw","next":"second-factor","methods":["totp"],"paths":[{"type":"totp","required_properties":[]}]}},"4. A recovery code opened recovery: enrol a new passkey":{"description":"4. A recovery code opened recovery: enrol a new passkey","value":{"error":"insufficient_authorization","auth_session":"Rm8Qx2Lc5Vn9Bt1Kd4Hz7Wp3Gs6Fa0Ej8Uo2Ti5Rk1Yw","next":"recovery","methods":["passkey-registration"],"paths":[{"type":"passkey-enrolment","required_properties":[]}]}},"6. An activation link: enrol a passkey, or choose a password and enrol an app":{"description":"6. An activation link: enrol a passkey, or choose a password and enrol an app","value":{"error":"insufficient_authorization","auth_session":"Qa1Ws5Ed9Rf3Tg7Yh1Uj5Ik9Ol3Pz7Xc1Vb5Nm9Lk3Jh","next":"enrolment-required","methods":["passkey-registration","password-set"],"paths":[{"type":"passkey-enrolment","required_properties":[]},{"type":"totp-enrolment","required_properties":[]}]}},"7. Initial setup of a platform administrator: a passkey, or password + TOTP":{"description":"7. Initial setup of a platform administrator: a passkey, or password + TOTP","value":{"error":"insufficient_authorization","auth_session":"Gh2Jk6Lz0Xc4Vb8Nm2Qw6Er0Ty4Ui8Op2As6Df0Gh4Jk","next":"setup","methods":["passkey-registration","password-set"],"paths":[{"type":"passkey-enrolment","required_properties":[]},{"type":"totp-enrolment","required_properties":[]}]}},"8. Initial setup of a tenant account: the factor is enrolled, choose a password":{"description":"8. Initial setup of a tenant account: the factor is enrolled, choose a password","value":{"error":"insufficient_authorization","auth_session":"Tb3Yn7Um1Ik5Ol9Pz3Xc7Vb1Nm5Qw9Er3Ty7Ui1Op5As9","next":"setup","methods":["password-set"],"paths":[],"recovery_codes":["7K3M-Q9TD-2XWH-B8NR-5FCJ-ZP4V","3HQW-8NVD-K2ZX-P7MR-4BTC-L9FJ"]}},"9. The creation options of a new passkey":{"description":"9. The creation options of a new passkey","value":{"error":"insufficient_authorization","auth_session":"Wq3Ne7Ry1Tb5Ux9Ic2Od6Pf0Ag4Sh8Jk2Ll6Zm0Xn4Cb","next":"enrolment-required","methods":["passkey-registration"],"public_key_creation":{"challenge":"q4Zl8w0XQm1vYp2kR7tN3sBfHcJ6uD9eA5gLxVoWiE0","rp":{"id":"dev.ankatech.co","name":"ANKASecure"},"user":{"id":"Xw3k9VhQ0mT2Lr5pYc8aN1sJ7dFb4GzKe6uHxWqRo0y","name":"maria.lopez@acme.example","displayName":"Maria Lopez"},"pubKeyCredParams":[{"type":"public-key","alg":-7},{"type":"public-key","alg":-8},{"type":"public-key","alg":-257}],"timeout":300000,"excludeCredentials":[],"authenticatorSelection":{"residentKey":"required","requireResidentKey":true,"userVerification":"required"},"attestation":"none","extensions":{"credProps":true}}}},"10. The secret of an authenticator app being enrolled":{"description":"10. The secret of an authenticator app being enrolled","value":{"error":"insufficient_authorization","auth_session":"Mx2Bn6Vc0Zl4Kj8Hg1Fd5Sa9Qw3Er7Ty1Ui5Op9As3Df","next":"enrolment-required","methods":["totp-enrolment-verification"],"totp":{"secret":"JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP","otpauth_uri":"otpauth://totp/ANKASecure:jane.doe%40acme.example?secret=JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP&issuer=ANKASecure&algorithm=SHA1&digits=6&period=30"}}},"12. The tenant is entered through its identity provider only":{"description":"12. The tenant is entered through its identity provider only","value":{"error":"insufficient_authorization","auth_session":"Lq6Wd0Fx4Nc8Vb2Mz7Kp1Rt5Gs9Hy3Ju7Ei1Oa5Tl9Ux","next":"second-factor","methods":["federated"],"paths":[{"type":"federated","required_properties":[],"idp_id":"3f1c2a9e-4b7d-4e2a-9c11-0a2b3c4d5e6f","display_name":"Acme Logistics single sign-on"}]}},"13. A choice between a passkey and the tenant's identity provider":{"description":"13. A choice between a passkey and the tenant's identity provider","value":{"error":"insufficient_authorization","auth_session":"Ve2Rn6Ty0Ui4Op8As2Df6Gh0Jk4Lz8Xc2Vb6Nm0Qw4Er","next":"second-factor","methods":["passkey","federated"],"public_key":{"challenge":"q4Zl8w0XQm1vYp2kR7tN3sBfHcJ6uD9eA5gLxVoWiE0","rpId":"dev.ankatech.co","timeout":300000,"userVerification":"required","allowCredentials":[]},"paths":[{"type":"passkey-assertion","required_properties":[]},{"type":"federated","required_properties":[],"idp_id":"3f1c2a9e-4b7d-4e2a-9c11-0a2b3c4d5e6f","display_name":"Acme Logistics single sign-on"}]}},"14. The federated path was taken: continue in the browser":{"description":"14. The federated path was taken: continue in the browser","value":{"error":"redirect_to_web","request_uri":"urn:ietf:params:oauth:request_uri:d89yKbbI2vRXomFstVcabunGNHLZvtzOKeZp5xDbX90","expires_in":60}},"15. Emergency access: a security key or device-bound passkey is asked for":{"description":"15. Emergency access: a security key or device-bound passkey is asked for","value":{"error":"insufficient_authorization","auth_session":"Fr8Tg2Hy6Ju0Ki4Lo8Pz2Xc6Vb0Nm4Qw8Er2Ty6Ui0Op4","next":"second-factor","methods":["passkey"],"public_key":{"challenge":"q4Zl8w0XQm1vYp2kR7tN3sBfHcJ6uD9eA5gLxVoWiE0","rpId":"dev.ankatech.co","timeout":300000,"userVerification":"required","allowCredentials":[]},"paths":[{"type":"passkey-assertion","required_properties":[]}]}},"5. The tenant requires an attested, hardware-backed passkey":{"description":"5. The tenant requires an attested, hardware-backed passkey","value":{"error":"insufficient_authorization","auth_session":"Zt5Lm1qWc8Ny3Kd0Hr6Vb2Xp9Gs4Fa7Ej1Uo3Ti8Rk0","next":"enrolment-required","methods":["passkey-registration"],"paths":[{"type":"passkey-enrolment","required_properties":["attested","hardware_backed"],"missing_properties":["attested","hardware_backed"]}]}}}},"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"11. Request from another origin":{"description":"11. Request from another origin","value":{"type":"https://docs.ankatech.co/errors/forbidden-origin","title":"Forbidden Origin","status":403,"detail":"Sign-in requests are accepted only from the origin this service is published on.","instance":"/api/v3/auth/authorize-challenge","timestamp":1790503200}}}}}},"503":{"description":"`/problems/dependency-not-ready`: a dependency the step needs (the credential service, the session store or the audit record) is unavailable. Nothing was issued and no step was skipped; start the sign-in again after `Retry-After`.","headers":{"Retry-After":{"description":"Seconds to wait before starting again","style":"simple","schema":{"type":"integer","example":30}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The credential service is unavailable":{"description":"1. The credential service is unavailable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/authorize-challenge"}}}}}}},"security":[]}},"/api/v3/auth/account/sessions/revoke-others":{"post":{"tags":["Account Sessions"],"summary":"End all your other sessions","description":"Ends every listed session of the person the access token names, in every tenant, except the\nsession this request comes from, which keeps working. Each ended session is ended as by\n<code>revokeAccountSession</code>.\n\nA session the list does not show — one whose refresh token was last rotated by the previous\nrelease while both releases ran side by side during an upgrade — is not reached by this\noperation; <code>revokeAllAccountSessions</code> reaches it.\n\nThe access token must identify the session it belongs to (its <code>sid</code> claim). An access\ntoken issued before the platform kept sessions does not, so the calling session cannot be told\nfrom the others: the request is refused with <code>409</code> and nothing changes. Signing in\nagain issues a token that identifies its session.\n\nThe revocation is recorded as <code>auth.session.revoked-others</code> before the answer.\n","operationId":"revokeOtherAccountSessions","responses":{"204":{"description":"Every other session was ended. Also 204 when there was none."},"401":{"description":"No access token was presented, or the one presented is not accepted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"`impersonation-operation-forbidden`: the access token is an impersonation token, and nothing changes. `forbidden`: the access token is not a person's session.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/sessions","timestamp":1765057500}}}}}},"409":{"description":"`session-not-identified`: the access token does not identify the session it belongs to (it carries no `sid`), so the calling session cannot be told from the others. Nothing changed; sign in again and repeat the request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. A token issued before sessions were kept":{"description":"1. A token issued before sessions were kept","value":{"type":"https://docs.ankatech.co/errors/session-not-identified","title":"Session Not Identified","status":409,"detail":"Your access token does not identify the session it belongs to, so your other sessions cannot be told apart from this one. Sign in again, then repeat the request.","instance":"/api/v3/auth/account/sessions/revoke-others","timestamp":1765057500}}}}}},"503":{"description":"`/problems/dependency-not-ready`: the session store could not be read or written, and nothing changed; or, for a revocation, its audit record could not be confirmed after the sessions were ended, which stands; or an administrative workload's DPoP proof could not be checked for replay, and nothing was read or changed. Retry after `Retry-After`.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"5"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Session store unavailable":{"description":"1. Session store unavailable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/sessions/revoke-others"}}}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/account/sessions/revoke-all":{"post":{"tags":["Account Sessions"],"summary":"End all your sessions","description":"Ends every session of the person the access token names, in every tenant, the session this\nrequest comes from included. Every access token and refresh token the person held before the\nrequest is refused from the answer on, including those of a session the list does not show. A\nsign-in made afterwards works.\n\nThe revocation is recorded as <code>auth.session.revoked-all</code> before the answer.\n","operationId":"revokeAllAccountSessions","responses":{"204":{"description":"Every session was ended."},"401":{"description":"No access token was presented, or the one presented is not accepted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"`impersonation-operation-forbidden`: the access token is an impersonation token, and nothing changes. `forbidden`: the access token is not a person's session.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/sessions","timestamp":1765057500}}}}}},"503":{"description":"`/problems/dependency-not-ready`: the session store could not be read or written, and nothing changed; or, for a revocation, its audit record could not be confirmed after the sessions were ended, which stands; or an administrative workload's DPoP proof could not be checked for replay, and nothing was read or changed. Retry after `Retry-After`.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"5"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Session store unavailable":{"description":"1. Session store unavailable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/sessions/revoke-others"}}}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/account/recovery-codes":{"post":{"tags":["Account Security"],"summary":"Replace your recovery codes","description":"Replaces every recovery code of the person the access token names with ten new single-use codes,\nanswered once. Every earlier code, used or not, is refused from the answer on. A recovery code is\nthe way back in when every other authenticator is lost: it opens a sign-in in which only the\nenrolment of a new authenticator is admitted.\n\nShow the codes to the person and do not keep them. The answer is never stored by a cache\n(<code>Cache-Control: no-store</code>).\n\nSensitive: <code>x-ankasecure-fresh-authentication: required</code>.\n","operationId":"regenerateRecoveryCodes","responses":{"200":{"description":"The new set, shown once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountAuthenticatorRecoveryCodesResponse"},"examples":{"1. Ten new single-use codes":{"description":"1. Ten new single-use codes","value":{"recoveryCodes":["M4QX-7TZB-1KWD-9HRN-C5LP-3VJF","T8NC-2PDK-6XWQ-4BZM-R9HL-1FSV","G5RW-9LXT-3QZD-7MKC-N1BP-6HJV","K2DZ-5HQM-8TWN-1RXC-B7LP-4FSJ","P9VB-4CMK-2ZTX-6WRD-H3QN-8LJF","X1LT-6RZQ-9DHW-3KBM-C5NP-7VGS","N7HK-3WQD-5BXZ-8TCR-M2LP-9FJV","C6QP-1XNM-4TKZ-7DWR-B9HL-2VSJ","J3ZW-8KDT-6RQX-2MBC-N5HP-1LVF","V5BR-2TLQ-7WKZ-9DXM-H4CN-6PJS"]}}}}}},"401":{"description":"Two causes, told apart by the RFC 9457 `type`. No access token was presented, or the one presented is expired, revoked or otherwise not accepted. Or (`reauthentication-required`) the person's last authentication is older than the reauthentication window (`auth.reauthentication.max-age-seconds`, 300 seconds by default): the answer carries `WWW-Authenticate: Bearer error=\"insufficient_user_authentication\", error_description=\"...\", max_age=\"<seconds>\"` and the window in `extensions.maxAge`; nothing is read or changed, and the person reauthenticates (`max_age` on the `refresh_token` grant) and retries. An impersonation token is refused with `403` instead, and tokens of applications and actors are never refused for it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The last authentication is older than the window":{"description":"1. The last authentication is older than the window","value":{"type":"https://docs.ankatech.co/errors/reauthentication-required","title":"Reauthentication Required","status":401,"detail":"This operation requires a more recent authentication. Reauthenticate and retry the request; nothing was changed.","instance":"/api/v3/auth/account/recovery-codes","timestamp":1765057500,"extensions":{"maxAge":300}}}}}}},"403":{"description":"Two causes, told apart by the RFC 9457 `type`. `impersonation-operation-forbidden`: the access token is an impersonation token; the person it names is the tenant's own user, so the operation is refused before anything is read or changed. `forbidden`: the access token is not a person's session (it records no sign-in method).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/authenticators","timestamp":1765057500}}}}}},"404":{"description":"The identity the access token names no longer exists.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"502":{"description":"The account service could not be reached, or answered outside the contract of this operation. Nothing is reported as changed; repeat the request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"504":{"description":"The account service did not answer within this operation's deadline. Repeat the request; a change that completed meanwhile is visible in the list.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready`: the account service does not serve this operation yet (a platform being upgraded runs the previous release of it for a while). Nothing changed; repeat the request after `Retry-After`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The account service runs the previous release":{"description":"1. The account service runs the previous release","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/authenticators"}}}}}}},"security":[{"bearerAuth":[]}],"x-ankasecure-fresh-authentication":"required"}},"/api/v3/auth/account/password/reset":{"post":{"tags":["Account Lifecycle"],"summary":"Complete password reset","description":"Validates reset token and sets the new password. Invalidates all active sessions.\n\nThe password is the person's one credential, so the reset applies in every tenant the person\nis a member of, and the sessions refused are those of every tenant: no refresh token issued\nbefore the reset refreshes again, whichever tenant it was issued for.\n\n**Security:**\n- Validates single-use reset token (JWT with 1h expiration)\n- Revokes every existing token of the person, in every tenant (force re-authentication)\n- The password policy decides the new password: 15 to 128 characters, no composition rule, no\n  common or compromised value, not containing the local part of the e-mail address\n- Token blacklisted after successful reset to prevent reuse\n\n**Process:**\n1. Validates reset token (signature, expiration, purpose claim)\n2. Checks token hasn't been used (single-use, enforced through the token blacklist)\n3. Updates the person's password with secure PBKDF2 hashing\n4. Revokes every active token of the person, in every tenant\n5. Adds reset token to blacklist\n6. Returns success confirmation\n","operationId":"resetPassword","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Password reset successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordResponse"},"examples":{"Successful Reset":{"description":"Successful Reset","value":{"success":true,"message":"Password has been reset successfully","userId":"7c9e6679-7425-40de-944b-e07fc1f90ae7","email":"admin@acmecorp.com"}}}}}},"400":{"description":"Invalid request data, a password the policy refuses, an invalid / malformed reset token, or a reused password. Distinguish by the RFC 7807 `type`: `validation` (a missing member), `password-policy-violation` (the password policy, the rule named in `extensions.rule`; the reset token stays unspent), `password-reused` (password history policy), or `invalid-token`. Invalid, malformed, not-yet-valid, unknown-user, deleted-account and tenant-mismatch tokens all collapse to ONE byte-identical `invalid-token` body (oracle-safe, ASVS 2.1.11).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Invalid Reset Token":{"description":"Invalid Reset Token","value":{"type":"https://docs.ankatech.co/errors/invalid-token","title":"Invalid Token","status":400,"detail":"The activation or reset link is invalid.","instance":"/api/v3/auth/account/password/reset","timestamp":1765057500}},"Password Policy Violation (too short)":{"description":"Password Policy Violation (too short)","value":{"type":"https://docs.ankatech.co/errors/password-policy-violation","title":"Password Policy Violation","status":400,"detail":"The password must be at least 15 characters long.","instance":"/api/v3/auth/account/password/reset","timestamp":1765057500,"extensions":{"rule":"too-short"}}},"Password Reused":{"description":"Password Reused","value":{"type":"https://docs.ankatech.co/errors/password-reused","title":"Password Reused","status":400,"detail":"The new password was used previously. Please choose a different password.","instance":"/api/v3/auth/account/password/reset","timestamp":1765057500}}}}}},"409":{"description":"The reset token has already been used.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Token Already Used":{"description":"Token Already Used","value":{"type":"https://docs.ankatech.co/errors/token-already-used","title":"Token Already Used","status":409,"detail":"This activation or reset link has already been used.","instance":"/api/v3/auth/account/password/reset","timestamp":1765057500}}}}}},"410":{"description":"The reset token has expired. Request a new password-reset link.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Token Expired":{"description":"Token Expired","value":{"type":"https://docs.ankatech.co/errors/token-expired","title":"Token Expired","status":410,"detail":"The activation or reset link has expired. Please request a new one.","instance":"/api/v3/auth/account/password/reset","timestamp":1765057500}}}}}},"413":{"description":"Payload Too Large. The JSON request body exceeds this service's limit of 262144 bytes. A request whose Content-Length is over the limit is refused before its body is read; a body sent without one is refused when the read crosses the limit. Authentication and authorization are decided first, so an unauthenticated request is answered 401 whatever its size. The body names only the limit, never the size that was sent.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"JSON body over the service's limit":{"summary":"A request body larger than the limit, refused before it reached the operation","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload Too Large","status":413,"detail":"Request body size exceeds the maximum allowed limit of 262144 bytes.","instance":"/api/v3/auth/account/password/reset","timestamp":1765057500}}}}}}}}},"/api/v3/auth/account/password/forgot":{"post":{"tags":["Account Lifecycle"],"summary":"Request password reset link","description":"Sends a password-reset link to the address if a person signs in with it.\n\n**The request carries the e-mail and nothing else.** A person is one identity with one\ncredential, whatever number of tenants they belong to, so there is no tenant to name. A\npayload naming a tenant — or carrying any member other than <code>email</code> — is refused\nwith a <code>400</code>: this operation is pre-authentication, so a tenant field would either\ndemand an identifier the person does not have, or become the tenant-enumeration oracle the\npost-authentication tenant picker is filtered to prevent.\n\n**One link per person.** The address names one identity, and that identity receives exactly\none link and one notification, however many tenants it is a member of. The link resets the\none credential the person uses in every tenant. A person who signs in only through an\nidentity provider and has no password yet sets a first password through the same link.\n\n**Security:**\n- The <code>202</code> is unconditional and its body is a fixed constant, identical for\n  a match in several tenants, in exactly one, in none, and for an internal failure\n  (OWASP ASVS 2.1.11). The body never echoes the submitted address\n- Covered by the service-wide request rate limiter; no per-endpoint override\n- Response timing is deliberately NOT equalized, and the defence does not rest on it:\n  issuing and sending a link costs measurably different work than finding no identity,\n  and equalizing that needs real mechanism rather than an assertion\n- The reset token is single-use and expires in 1 hour (configurable)\n\n**Process:**\n1. Validates the e-mail's shape (the only rejection this endpoint makes)\n2. Resolves the one identity the address names (internal, never disclosed)\n3. Generates one single-use reset token for it\n4. Publishes one reset mail\n5. Returns the same generic acknowledgement whatever step 2 found\n","operationId":"forgotPassword","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordRequest"}}},"required":true},"responses":{"202":{"description":"Request processed. Check email if account exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordResponse"},"examples":{"Generic acknowledgement (identical for every outcome)":{"description":"Generic acknowledgement (identical for every outcome)","value":{"message":"If the email address exists in our system, a password reset link has been sent"}}}}}},"400":{"description":"The request was refused before any lookup happened, so this status says nothing about whether the address exists. TWO causes, both structural: a payload carrying a member this request does not accept — a tenant identifier is the one callers reach for, and this request deliberately does not accept it — and an `email` that is absent, blank, longer than 255 characters or not a valid address. Both are decided by request validation before the operation runs, which is why they are a 400 rather than the unconditional 202 every post-lookup outcome shares.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The body carried a member this request does not accept":{"description":"1. The body carried a member this request does not accept","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Validation Error","status":400,"detail":"Malformed request body: unable to parse JSON","instance":"/api/v3/auth/account/password/forgot","timestamp":1765057500}},"2. Malformed e-mail address":{"description":"2. Malformed e-mail address","value":{"type":"https://docs.ankatech.co/errors/validation","title":"Validation Error","status":400,"detail":"'email' must be a well-formed email address;","instance":"/api/v3/auth/account/password/forgot","timestamp":1765057500}}}}}},"413":{"description":"Payload Too Large. The JSON request body exceeds this service's limit of 262144 bytes. A request whose Content-Length is over the limit is refused before its body is read; a body sent without one is refused when the read crosses the limit. Authentication and authorization are decided first, so an unauthenticated request is answered 401 whatever its size. The body names only the limit, never the size that was sent.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"JSON body over the service's limit":{"summary":"A request body larger than the limit, refused before it reached the operation","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload Too Large","status":413,"detail":"Request body size exceeds the maximum allowed limit of 262144 bytes.","instance":"/api/v3/auth/account/password/forgot","timestamp":1765057500}}}}}}}}},"/api/v3/auth/account/password/change":{"post":{"tags":["Account Lifecycle"],"summary":"Change your own password","description":"Replaces the password of the person named by the access token presented with this\nrequest. It changes no other person's password: the request body carries no user, e-mail\nor tenant identifier, so there is nothing in it to aim elsewhere.\n\n**One credential, every tenant.** A person holds one password, used in every tenant they are\na member of, so a change made while signed in to one tenant applies to all of them.\n\n**The current password is required even though the session is already valid.**\nReauthentication on a credential change is the settled industry pattern (OWASP ASVS\n2.1.x; Keycloak, Auth0 and Okta all require it) and it is what stops a stolen access\ntoken from taking the account over permanently.\n\n**Password policy** (after NIST SP 800-63B-4 &sect;3.1.1.2) &mdash; 15 to 128 characters, no\ncomposition rule (<code>harbour lantern quietly folding</code> is accepted), not a common or\ncompromised password, and not containing the local part of the person's e-mail address. The\nsame policy, with the same answer, applies wherever a password is set. A new password that\nbreaks it is <code>400 password-policy-violation</code> naming the rule in\n<code>extensions.rule</code> (<code>too-short</code>, <code>too-long</code>,\n<code>blocklisted</code>, <code>context-word</code>). The policy is decided before the\ncurrent password is checked, so that answer says nothing about the current password. The\nnew password must also differ from the current one and from the last 5.\n\n**One refusal for two causes.** A wrong current password and a new password that repeats a\nrecent one answer with a byte-identical <code>400 invalid-password-change</code>, so a\ncaller cannot tell which was true. A current password longer than 1024 characters is the\nsame refusal. A subject whose credential is held by an external identity provider is\nrefused differently, with <code>409</code> and its own problem type: telling such a caller\ntheir password is wrong for an account that has none would be untrue and unactionable.\n\nSensitive: <code>x-ankasecure-fresh-authentication: required</code>. A person whose last\nauthentication is older than the reauthentication window is answered <code>401 reauthentication-required</code> with the RFC 9470 challenge, before\nthe current password is checked; the current password is still required with a recent one.\n\n**Not available while impersonating.** A support session acting on behalf of a tenant is\nrefused with <code>403</code> on every deployment type, whatever roles that session\ncarries. The account this operation changes is the one the token names, and under\nimpersonation that is the tenant's own user rather than the operator holding the session.\nThe refusal happens before the current password is checked, so an impersonated attempt\ncounts toward nobody's lockout.\n\nNote the ONE case where an impersonated request is not answered <code>403</code>: request\nbinding and validation run before this operation does, so an impersonated request whose\nbody is malformed or missing a required member is answered <code>400</code> — the body\nnever reaches the point where the session is classified. No password is read, verified\nor replaced either way, and the <code>400</code> is not an indication that the operation\nwas available. The support-access trail records it as the refusal it is, attributed to\nthe operator who made the request: what the platform writes about a request is decided\nby the session, not by which of the two statuses that request happened to reach.\n\n**On success every live token of the person is refused from that moment on, in every\ntenant,** and the caller is expected to sign in again. A refusal writes nothing.\n","operationId":"changePassword","requestBody":{"description":"The caller's current password and its replacement. No account identifier: the subject is the one named by the access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"},"examples":{"Change your own password":{"summary":"Prove the current password, supply a policy-compliant replacement","description":"Change your own password","value":{"currentPassword":"Qu1etHarbour!Winter","newPassword":"harbour lantern quietly folding"}}}}},"required":true},"responses":{"204":{"description":"Password changed. Every previously issued token for this account is now refused, so the caller must sign in again. No body is returned."},"400":{"description":"The change was refused. Told apart by the RFC 9457 `type`. `password-policy-violation`: the new password breaks the password policy, the rule named in `extensions.rule`; decided before the current password is checked. `invalid-password-change`: ONE body for two causes — a wrong current password and a new password that repeats the current one or a recent one — so the response reveals nothing about which was true (OWASP ASVS 2.1.11); a current password longer than 1024 characters is the same body. A structurally malformed body (a missing member, or a member this request does not accept) is a 400 with the validation type.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Refused: wrong current password, or reuse":{"description":"1. Refused: wrong current password, or reuse","value":{"type":"https://docs.ankatech.co/errors/invalid-password-change","title":"Invalid Password Change","status":400,"detail":"The password change was refused. Check the current password and make sure the new password meets the password policy and has not been used recently.","instance":"/api/v3/auth/account/password/change","timestamp":1765057500}},"2. Refused: the new password breaks the password policy (too short)":{"description":"2. Refused: the new password breaks the password policy (too short)","value":{"type":"https://docs.ankatech.co/errors/password-policy-violation","title":"Password Policy Violation","status":400,"detail":"The password must be at least 15 characters long.","instance":"/api/v3/auth/account/password/change","timestamp":1765057500,"extensions":{"rule":"too-short"}}},"3. Refused: the body carried a member this request does not accept":{"description":"3. Refused: the body carried a member this request does not accept","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Validation Error","status":400,"detail":"Malformed request body: unable to parse JSON","instance":"/api/v3/auth/account/password/change","timestamp":1765057500}}}}}},"401":{"description":"Two causes, told apart by the RFC 9457 `type`. No access token was presented, or the one presented is expired, revoked or otherwise not accepted. Or (`reauthentication-required`) the person's last authentication is older than the reauthentication window (`auth.reauthentication.max-age-seconds`, 300 seconds by default): the answer carries `WWW-Authenticate: Bearer error=\"insufficient_user_authentication\", error_description=\"...\", max_age=\"<seconds>\"` and the window in `extensions.maxAge`; nothing is read or changed, and the person reauthenticates (`max_age` on the `refresh_token` grant) and retries. An impersonation token is refused with `403` instead, and tokens of applications and actors are never refused for it. The current password is not checked when the authentication is not recent enough. This endpoint is authenticated; it is NOT one of the public account paths.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. No credential presented":{"description":"1. No credential presented","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication is required to call this endpoint.","instance":"/api/v3/auth/account/password/change","timestamp":1765057500}},"2. The last authentication is older than the window":{"description":"2. The last authentication is older than the window","value":{"type":"https://docs.ankatech.co/errors/reauthentication-required","title":"Reauthentication Required","status":401,"detail":"This operation requires a more recent authentication. Reauthenticate and retry the request; nothing was changed.","instance":"/api/v3/auth/account/password/change","timestamp":1765057500,"extensions":{"maxAge":300}}}}}}},"403":{"description":"The access token presented is an impersonation token: the caller is acting on behalf of a tenant. This operation changes the password of the account the token names, which under impersonation is the tenant's own user — so it is withheld from a support session by design, on EVERY deployment type and whatever roles that session carries. The current password is never checked, so the attempt counts toward no account's lockout.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/password/change","timestamp":1765057500}}}}}},"409":{"description":"This account authenticates through an external identity provider and has no password managed by ANKASecure. Deliberately distinguishable from the 400 above, and no local credential is created on this path; a first password is set through the forgot-password link.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Federated-only account":{"description":"1. Federated-only account","value":{"type":"https://docs.ankatech.co/errors/no-local-credential","title":"No Local Password","status":409,"detail":"This account authenticates through your organization's identity provider and has no password managed by ANKASecure. Change it with your identity provider.","instance":"/api/v3/auth/account/password/change","timestamp":1765057500}}}}}},"502":{"description":"The account service could not be reached, or answered outside the contract of this operation. This is an outage of ours, reported as one — it is deliberately NOT presented as a 401, which would read as an expired session and send the caller to re-authenticate a session that is perfectly healthy.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"504":{"description":"The account service did not answer within this operation's deadline.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"413":{"description":"Payload Too Large. The JSON request body exceeds this service's limit of 262144 bytes. A request whose Content-Length is over the limit is refused before its body is read; a body sent without one is refused when the read crosses the limit. Authentication and authorization are decided first, so an unauthenticated request is answered 401 whatever its size. The body names only the limit, never the size that was sent.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"JSON body over the service's limit":{"summary":"A request body larger than the limit, refused before it reached the operation","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload Too Large","status":413,"detail":"Request body size exceeds the maximum allowed limit of 262144 bytes.","instance":"/api/v3/auth/account/password/change","timestamp":1765057500}}}}}}},"x-ankasecure-fresh-authentication":"required"}},"/api/v3/auth/account/authenticators/totp":{"post":{"tags":["Account Security"],"summary":"Start enrolling an authenticator app","description":"Starts enrolling an authenticator app (TOTP, RFC 6238: SHA-1, six digits, 30-second period) for the\nperson the access token names. The answer carries the new secret and its <code>otpauth://</code>\nURI, once: render the URI as a QR code, or show the secret for manual entry. The factor is enabled\nonly when <code>verifyTotpEnrolment</code> accepts a code from it; an enrolment never verified is\nreplaced by the next one.\n\nAn authenticator app is a second factor after a password. It is not phishing resistant: a passkey is\noffered first wherever one is admitted. A person who already holds a verified authenticator app is refused with\n<code>409</code>: remove it first.\n\nThe answer is never stored by a cache (<code>Cache-Control: no-store</code>).\n\nSensitive: <code>x-ankasecure-fresh-authentication: required</code>.\n","operationId":"createTotpEnrolment","responses":{"201":{"description":"The enrolment is started; the secret is shown once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountAuthenticatorTotpEnrolmentResponse"},"examples":{"1. The secret and its otpauth URI":{"description":"1. The secret and its otpauth URI","value":{"secret":"JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP","otpauthUri":"otpauth://totp/ANKASecure:maria.lopez%40acme.example?secret=JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP&issuer=ANKASecure&algorithm=SHA1&digits=6&period=30"}}}}}},"401":{"description":"Two causes, told apart by the RFC 9457 `type`. No access token was presented, or the one presented is expired, revoked or otherwise not accepted. Or (`reauthentication-required`) the person's last authentication is older than the reauthentication window (`auth.reauthentication.max-age-seconds`, 300 seconds by default): the answer carries `WWW-Authenticate: Bearer error=\"insufficient_user_authentication\", error_description=\"...\", max_age=\"<seconds>\"` and the window in `extensions.maxAge`; nothing is read or changed, and the person reauthenticates (`max_age` on the `refresh_token` grant) and retries. An impersonation token is refused with `403` instead, and tokens of applications and actors are never refused for it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The last authentication is older than the window":{"description":"1. The last authentication is older than the window","value":{"type":"https://docs.ankatech.co/errors/reauthentication-required","title":"Reauthentication Required","status":401,"detail":"This operation requires a more recent authentication. Reauthenticate and retry the request; nothing was changed.","instance":"/api/v3/auth/account/recovery-codes","timestamp":1765057500,"extensions":{"maxAge":300}}}}}}},"403":{"description":"Two causes, told apart by the RFC 9457 `type`. `impersonation-operation-forbidden`: the access token is an impersonation token; the person it names is the tenant's own user, so the operation is refused before anything is read or changed. `forbidden`: the access token is not a person's session (it records no sign-in method).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/authenticators","timestamp":1765057500}}}}}},"404":{"description":"The identity the access token names no longer exists.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"The person already holds a verified authenticator app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. An authenticator app is already enrolled":{"description":"1. An authenticator app is already enrolled","value":{"type":"https://docs.ankatech.co/errors/conflict","title":"Conflict","status":409,"detail":"A verified TOTP authenticator is already enrolled.","instance":"/api/v3/auth/account/authenticators/totp","timestamp":1765057500}}}}}},"502":{"description":"The account service could not be reached, or answered outside the contract of this operation. Nothing is reported as changed; repeat the request. Also the answer when this deployment cannot seal the secret (no key is bound for it).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"504":{"description":"The account service did not answer within this operation's deadline. Repeat the request; a change that completed meanwhile is visible in the list.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready`: the account service does not serve this operation yet (a platform being upgraded runs the previous release of it for a while). Nothing changed; repeat the request after `Retry-After`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The account service runs the previous release":{"description":"1. The account service runs the previous release","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/authenticators"}}}}}}},"security":[{"bearerAuth":[]}],"x-ankasecure-fresh-authentication":"required"}},"/api/v3/auth/account/authenticators/totp/verification":{"post":{"tags":["Account Security"],"summary":"Complete enrolling an authenticator app","description":"Completes the enrolment started by <code>createTotpEnrolment</code> with the code the authenticator\napp shows now. A valid code enables the factor: <code>verified: true</code>. A code that is not\nvalid is <code>verified: false</code>, the enrolment stays pending, and the next code may be\nsubmitted.\n\n**The first strong factor.** When the authenticator app is the person's first passkey or\nauthenticator app, the answer carries the first ten single-use recovery codes, once. The answer is\nnever stored by a cache (<code>Cache-Control: no-store</code>).\n\nSensitive: <code>x-ankasecure-fresh-authentication: required</code>.\n","operationId":"verifyTotpEnrolment","requestBody":{"description":"The one-time code the authenticator app shows, as `otp`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountAuthenticatorTotpVerificationRequest"},"examples":{"1. The current code":{"description":"1. The current code","value":{"otp":"492039"}}}}},"required":true},"responses":{"200":{"description":"The verdict, with the first recovery codes when issued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountAuthenticatorTotpVerificationResponse"},"examples":{"1. Enabled as the first strong factor: the recovery codes, shown once":{"description":"1. Enabled as the first strong factor: the recovery codes, shown once","value":{"verified":true,"recoveryCodes":["7K3M-Q9TD-2XWH-B8NR-5FCJ-ZP4V","H2PW-6LQX-9TRC-D4MB-KV8N-3JZF","R5TY-W1QZ-M8KD-4HNC-X7PL-2VBG","B9JS-3KDW-T6QF-N2XR-5MZC-H8LP","F4NV-8RCQ-2LZK-W7TH-D5XM-9BJS","Q6ZD-1WMT-5HXR-K9PB-3NLC-7VGF","L8CX-4TBN-7QWK-2DZR-M6HJ-1PSV","W3HR-9MKZ-6BXT-5QLD-N2CF-8JVP","D7PB-2XKN-4RZW-9TCM-H5QL-6FSJ","Z1VM-5QHC-8KWX-3BTD-R7NP-4LJG"]}},"2. Enabled beside a passkey":{"description":"2. Enabled beside a passkey","value":{"verified":true,"recoveryCodes":null}},"3. The code was not valid; submit the next one":{"description":"3. The code was not valid; submit the next one","value":{"verified":false,"recoveryCodes":null}}}}}},"400":{"description":"`validation`: `otp` is missing or longer than 16 characters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"Two causes, told apart by the RFC 9457 `type`. No access token was presented, or the one presented is expired, revoked or otherwise not accepted. Or (`reauthentication-required`) the person's last authentication is older than the reauthentication window (`auth.reauthentication.max-age-seconds`, 300 seconds by default): the answer carries `WWW-Authenticate: Bearer error=\"insufficient_user_authentication\", error_description=\"...\", max_age=\"<seconds>\"` and the window in `extensions.maxAge`; nothing is read or changed, and the person reauthenticates (`max_age` on the `refresh_token` grant) and retries. An impersonation token is refused with `403` instead, and tokens of applications and actors are never refused for it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The last authentication is older than the window":{"description":"1. The last authentication is older than the window","value":{"type":"https://docs.ankatech.co/errors/reauthentication-required","title":"Reauthentication Required","status":401,"detail":"This operation requires a more recent authentication. Reauthenticate and retry the request; nothing was changed.","instance":"/api/v3/auth/account/recovery-codes","timestamp":1765057500,"extensions":{"maxAge":300}}}}}}},"403":{"description":"Two causes, told apart by the RFC 9457 `type`. `impersonation-operation-forbidden`: the access token is an impersonation token; the person it names is the tenant's own user, so the operation is refused before anything is read or changed. `forbidden`: the access token is not a person's session (it records no sign-in method).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/authenticators","timestamp":1765057500}}}}}},"404":{"description":"No enrolment was started, or the identity the access token names no longer exists.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. No enrolment started":{"description":"1. No enrolment started","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"No TOTP enrolment was started.","instance":"/api/v3/auth/account/authenticators/totp/verification","timestamp":1765057500}}}}}},"409":{"description":"The authenticator app is already verified.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"502":{"description":"The account service could not be reached, or answered outside the contract of this operation. Nothing is reported as changed; repeat the request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"504":{"description":"The account service did not answer within this operation's deadline. Repeat the request; a change that completed meanwhile is visible in the list.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready`: the account service does not serve this operation yet (a platform being upgraded runs the previous release of it for a while). Nothing changed; repeat the request after `Retry-After`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The account service runs the previous release":{"description":"1. The account service runs the previous release","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/authenticators"}}}}}},"413":{"description":"Payload Too Large. The JSON request body exceeds this service's limit of 262144 bytes. A request whose Content-Length is over the limit is refused before its body is read; a body sent without one is refused when the read crosses the limit. Authentication and authorization are decided first, so an unauthenticated request is answered 401 whatever its size. The body names only the limit, never the size that was sent.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"JSON body over the service's limit":{"summary":"A request body larger than the limit, refused before it reached the operation","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload Too Large","status":413,"detail":"Request body size exceeds the maximum allowed limit of 262144 bytes.","instance":"/api/v3/auth/account/authenticators/totp/verification","timestamp":1765057500}}}}}}},"security":[{"bearerAuth":[]}],"x-ankasecure-fresh-authentication":"required"}},"/api/v3/auth/account/authenticators/passkeys":{"post":{"tags":["Account Security"],"summary":"Register a passkey","description":"Completes the registration ceremony started by <code>createPasskeyRegistrationOptions</code> in this\nsession: the platform verifies the WebAuthn registration response (W3C WebAuthn Level 3 §7.1) and\nstores the passkey under the name the person gives it.\n\n**What is verified.** The challenge must have been issued to this person in this session, and is\nconsumed; the origin must be this site; the RP ID hash must be this site's host; the user-verified\nflag must be set; and the credential must not be registered already. A response that fails any of\nthese is <code>400 passkey-registration-refused</code>, and nothing is stored: start a new\nregistration. When the options asked for attestation, the attestation must establish every\nproperty the tenant's policy requires; otherwise the answer is <code>422</code> and nothing is\nstored.\n\n**The first strong factor.** When the passkey is the person's first passkey or authenticator app,\nthe answer carries the person's first ten single-use recovery codes, once. Show them to the person\nand do not keep them. The answer is never stored by a cache (<code>Cache-Control: no-store</code>).\n\nSensitive: <code>x-ankasecure-fresh-authentication: required</code>.\n","operationId":"registerPasskey","requestBody":{"description":"The name the person gives the passkey and the registration response the browser returned. Binary members are unpadded base64url, as <code>PublicKeyCredential.toJSON()</code> writes them. No identity member: the person is the one the access token names.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountAuthenticatorPasskeyRegistrationRequest"},"examples":{"1. A passkey created on a laptop":{"description":"1. A passkey created on a laptop","value":{"displayName":"Work laptop","credential":{"id":"AV3kUf7zq1yJm0pXr9tB2cQe","clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoicTRabDh3MFhRbTF2WXAya1I3dE4zc0JmSGNKNnVEOWVBNWdMeFZvV2lFMCIsIm9yaWdpbiI6Imh0dHBzOi8vZGV2LmFua2F0ZWNoLmNvIn0","attestationObject":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBdAAAAAA","transports":["internal","hybrid"],"credPropsRk":true}}}}}},"required":true},"responses":{"201":{"description":"The passkey is stored. The first recovery codes ride on the answer when it is the person's first strong factor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountAuthenticatorPasskeyRegistrationResponse"},"examples":{"1. The first strong factor: the recovery codes, shown once":{"description":"1. The first strong factor: the recovery codes, shown once","value":{"authenticatorId":"7c1e4b2a-9d3f-4e6a-8b5c-2f1d0e9a7b64","displayName":"Work laptop","properties":{"phishingResistant":true,"userVerifying":true,"syncable":true,"deviceBound":false,"hardwareBacked":false,"attested":false,"discoverable":true},"recoveryCodes":["7K3M-Q9TD-2XWH-B8NR-5FCJ-ZP4V","H2PW-6LQX-9TRC-D4MB-KV8N-3JZF","R5TY-W1QZ-M8KD-4HNC-X7PL-2VBG","B9JS-3KDW-T6QF-N2XR-5MZC-H8LP","F4NV-8RCQ-2LZK-W7TH-D5XM-9BJS","Q6ZD-1WMT-5HXR-K9PB-3NLC-7VGF","L8CX-4TBN-7QWK-2DZR-M6HJ-1PSV","W3HR-9MKZ-6BXT-5QLD-N2CF-8JVP","D7PB-2XKN-4RZW-9TCM-H5QL-6FSJ","Z1VM-5QHC-8KWX-3BTD-R7NP-4LJG"]}},"2. Another passkey: an attested security key, no recovery codes":{"description":"2. Another passkey: an attested security key, no recovery codes","value":{"authenticatorId":"2b9d4e61-0c7a-4f3e-9a18-5d6c7b8a9e01","displayName":"Security key in the office safe","properties":{"phishingResistant":true,"userVerifying":true,"syncable":false,"deviceBound":true,"hardwareBacked":true,"attested":true,"discoverable":true},"recoveryCodes":null}}}}}},"400":{"description":"`passkey-registration-refused`: the registration response does not verify (another origin or RP ID, a challenge not issued in this session, user verification absent, a credential already registered), and nothing is stored. `validation`: a member is missing, too long or not base64url.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The registration does not verify":{"description":"1. The registration does not verify","value":{"type":"https://docs.ankatech.co/errors/passkey-registration-refused","title":"Passkey Registration Refused","status":400,"detail":"The passkey registration could not be verified; start a new registration.","instance":"/api/v3/auth/account/authenticators/passkeys","timestamp":1765057500}},"2. A member is missing":{"description":"2. A member is missing","value":{"type":"https://docs.ankatech.co/errors/validation","title":"Validation Error","status":400,"detail":"'displayName' must not be blank;","instance":"/api/v3/auth/account/authenticators/passkeys","timestamp":1765057500}}}}}},"401":{"description":"Two causes, told apart by the RFC 9457 `type`. No access token was presented, or the one presented is expired, revoked or otherwise not accepted. Or (`reauthentication-required`) the person's last authentication is older than the reauthentication window (`auth.reauthentication.max-age-seconds`, 300 seconds by default): the answer carries `WWW-Authenticate: Bearer error=\"insufficient_user_authentication\", error_description=\"...\", max_age=\"<seconds>\"` and the window in `extensions.maxAge`; nothing is read or changed, and the person reauthenticates (`max_age` on the `refresh_token` grant) and retries. An impersonation token is refused with `403` instead, and tokens of applications and actors are never refused for it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The last authentication is older than the window":{"description":"1. The last authentication is older than the window","value":{"type":"https://docs.ankatech.co/errors/reauthentication-required","title":"Reauthentication Required","status":401,"detail":"This operation requires a more recent authentication. Reauthenticate and retry the request; nothing was changed.","instance":"/api/v3/auth/account/recovery-codes","timestamp":1765057500,"extensions":{"maxAge":300}}}}}}},"403":{"description":"Two causes, told apart by the RFC 9457 `type`. `impersonation-operation-forbidden`: the access token is an impersonation token; the person it names is the tenant's own user, so the operation is refused before anything is read or changed. `forbidden`: the access token is not a person's session (it records no sign-in method).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/authenticators","timestamp":1765057500}}}}}},"404":{"description":"The identity the access token names no longer exists.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"`session-not-identified`: the access token does not identify the session it belongs to. Nothing is stored.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"422":{"description":"`passkey-property-not-established`: the options asked for attestation and it does not establish a property the tenant's policy requires; the detail names them. Nothing is stored.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The attestation does not establish a required property":{"description":"1. The attestation does not establish a required property","value":{"type":"https://docs.ankatech.co/errors/passkey-property-not-established","title":"Passkey Property Not Established","status":422,"detail":"The authenticator's attestation does not establish that the passkey is attested and hardware_backed; register a passkey on an authenticator that meets the policy.","instance":"/api/v3/auth/account/authenticators/passkeys","timestamp":1765057500}}}}}},"502":{"description":"The account service could not be reached, or answered outside the contract of this operation. Nothing is reported as changed; repeat the request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"504":{"description":"The account service did not answer within this operation's deadline. Repeat the request; a change that completed meanwhile is visible in the list.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready`: the account service does not serve this operation yet (a platform being upgraded runs the previous release of it for a while). Nothing changed; repeat the request after `Retry-After`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The account service runs the previous release":{"description":"1. The account service runs the previous release","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/authenticators"}}}}}},"413":{"description":"Payload Too Large. The JSON request body exceeds this service's limit of 262144 bytes. A request whose Content-Length is over the limit is refused before its body is read; a body sent without one is refused when the read crosses the limit. Authentication and authorization are decided first, so an unauthenticated request is answered 401 whatever its size. The body names only the limit, never the size that was sent.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"JSON body over the service's limit":{"summary":"A request body larger than the limit, refused before it reached the operation","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload Too Large","status":413,"detail":"Request body size exceeds the maximum allowed limit of 262144 bytes.","instance":"/api/v3/auth/account/authenticators/passkeys","timestamp":1765057500}}}}}}},"security":[{"bearerAuth":[]}],"x-ankasecure-fresh-authentication":"required"}},"/api/v3/auth/account/authenticators/passkeys/registration-options":{"post":{"tags":["Account Security"],"summary":"Start registering a passkey","description":"Issues the WebAuthn creation options of a new passkey for the person the access token names, the\nfirst half of a registration ceremony (W3C WebAuthn Level 3 §7.1). Pass the answer to\n<code>PublicKeyCredential.parseCreationOptionsFromJSON</code> and\n<code>navigator.credentials.create()</code>, then submit the result to <code>registerPasskey</code>.\n\n**Bound to this session.** The challenge is single-use and answers only inside the session this\nrequest comes from: options obtained in one session cannot complete a registration in another. An\naccess token that does not identify its session is refused with <code>409</code>; signing in again\nyields one that does.\n\n**What the options ask for.** <code>rp.id</code> is this site's host, <code>user.id</code> a random\nhandle generated once per person (never the identity id or the address), <code>residentKey</code>\nand <code>userVerification</code> are <code>required</code>, and <code>excludeCredentials</code>\nlists the person's passkeys so the same authenticator is not registered twice.\n<code>attestation</code> is <code>none</code>, unless the authentication policy of the tenant this\nsession is in requires an attested or hardware-backed passkey: it is then <code>direct</code>, and\nthe registration must establish those properties. When the policy requires them and this\ndeployment has no FIDO metadata loaded, nothing is issued and the answer is <code>422</code>.\n\n**Only on the deployment's host name.** A passkey belongs to the host name it is registered for, so\nthe ceremony runs only from the console opened on the deployment's Management host name. A request\nwhose <code>Origin</code> is not one of the deployment's Management addresses is refused with\n<code>403 forbidden-origin</code>; one from another Management address (such as the host's IP address)\nis refused with <code>409 passkey-unavailable-on-origin</code>. Nothing is issued either way.\n\nThe answer is never stored by a cache (<code>Cache-Control: no-store</code>).\n","operationId":"createPasskeyRegistrationOptions","responses":{"200":{"description":"The creation options of the ceremony.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicKeyCreationOptions"},"examples":{"1. A tenant whose policy accepts any passkey (attestation none)":{"description":"1. A tenant whose policy accepts any passkey (attestation none)","value":{"challenge":"q4Zl8w0XQm1vYp2kR7tN3sBfHcJ6uD9eA5gLxVoWiE0","rp":{"id":"dev.ankatech.co","name":"ANKASecure"},"user":{"id":"Xw3k9VhQ0mT2Lr5pYc8aN1sJ7dFb4GzKe6uHxWqRo0yBiMnCt3vDlE2fP9gA5hSjXw3k9VhQ0mT2Lr5pYc8aN1s","name":"maria.lopez@acme.example","displayName":"Maria Lopez"},"pubKeyCredParams":[{"type":"public-key","alg":-7},{"type":"public-key","alg":-8},{"type":"public-key","alg":-257}],"timeout":300000,"excludeCredentials":[{"type":"public-key","id":"AV3kUf7zq1yJm0pXr9tB2cQe","transports":["internal","hybrid"]}],"authenticatorSelection":{"residentKey":"required","requireResidentKey":true,"userVerification":"required"},"attestation":"none","extensions":{"credProps":true}}},"2. A tenant whose policy requires an attested hardware-backed passkey (attestation direct)":{"description":"2. A tenant whose policy requires an attested hardware-backed passkey (attestation direct)","value":{"challenge":"Zr8yN2bV5cX1mQ7wE4tY9uI3oP6aS0dF2gH5jK8lZxA","rp":{"id":"dev.ankatech.co","name":"ANKASecure"},"user":{"id":"Xw3k9VhQ0mT2Lr5pYc8aN1sJ7dFb4GzKe6uHxWqRo0yBiMnCt3vDlE2fP9gA5hSjXw3k9VhQ0mT2Lr5pYc8aN1s","name":"maria.lopez@acme.example","displayName":"Maria Lopez"},"pubKeyCredParams":[{"type":"public-key","alg":-7},{"type":"public-key","alg":-8},{"type":"public-key","alg":-257}],"timeout":300000,"excludeCredentials":[],"authenticatorSelection":{"residentKey":"required","requireResidentKey":true,"userVerification":"required"},"attestation":"direct","extensions":{"credProps":true}}}}}}},"401":{"description":"No access token was presented, or the one presented is expired, revoked or otherwise not accepted. Nothing is read or changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. No credential presented":{"description":"1. No credential presented","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication is required to call this endpoint.","instance":"/api/v3/auth/account/authenticators","timestamp":1765057500}}}}}},"403":{"description":"Two causes, told apart by the RFC 9457 `type`. `impersonation-operation-forbidden`: the access token is an impersonation token; the person it names is the tenant's own user, so the operation is refused before anything is read or changed. `forbidden`: the access token is not a person's session (it records no sign-in method). A third cause on this operation, `forbidden-origin`: the request's `Origin` is not one of the deployment's Management addresses. Nothing was issued.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/authenticators","timestamp":1765057500}},"2. A request from a page outside the deployment's Management addresses":{"description":"2. A request from a page outside the deployment's Management addresses","value":{"type":"https://docs.ankatech.co/errors/forbidden-origin","title":"Forbidden Origin","status":403,"detail":"Sign-in requests are accepted only from the origin this service is published on.","instance":"/api/v3/auth/account/authenticators/passkeys/registration-options"}}}}}},"404":{"description":"The identity the access token names no longer exists.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"`session-not-identified`: the access token does not identify the session it belongs to, so no challenge can be bound to it. `passkey-unavailable-on-origin`: the console is open on a Management address that is not the deployment's host name (such as the host's IP address), where no passkey can be registered; no `Retry-After`, the answer is the same until the console is opened on the host name. Nothing was issued.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. A token that names no session":{"description":"1. A token that names no session","value":{"type":"https://docs.ankatech.co/errors/session-not-identified","title":"Session Not Identified","status":409,"detail":"Your access token does not identify the session it belongs to, so your other sessions cannot be told apart from this one. Sign in again, then repeat the request.","instance":"/api/v3/auth/account/authenticators/passkeys/registration-options","timestamp":1765057500}},"2. The console is open on the host's IP address":{"description":"2. The console is open on the host's IP address","value":{"type":"https://docs.ankatech.co/errors/passkey-unavailable-on-origin","title":"Passkey Unavailable On This Address","status":409,"detail":"Passkeys cannot be used on this address of the deployment. Open the console on the deployment's host name to use a passkey, or sign in with a password and an authenticator app.","instance":"/api/v3/auth/account/authenticators/passkeys/registration-options"}}}}}},"422":{"description":"`passkey-property-not-established`: the tenant's policy requires an attested or hardware-backed passkey and this deployment has no FIDO metadata loaded; the detail names the properties. Nothing was issued.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Attestation required, no FIDO metadata loaded":{"description":"1. Attestation required, no FIDO metadata loaded","value":{"type":"https://docs.ankatech.co/errors/passkey-property-not-established","title":"Passkey Property Not Established","status":422,"detail":"A passkey must be attested and hardware_backed, which needs the FIDO metadata of this deployment, and none is loaded. A platform operator uploads the current FIDO metadata BLOB.","instance":"/api/v3/auth/account/authenticators/passkeys/registration-options","timestamp":1765057500}}}}}},"502":{"description":"The account service could not be reached, or answered outside the contract of this operation. Nothing is reported as changed; repeat the request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"504":{"description":"The account service did not answer within this operation's deadline. Repeat the request; a change that completed meanwhile is visible in the list.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready`: the account service does not serve this operation yet (a platform being upgraded runs the previous release of it for a while). Nothing changed; repeat the request after `Retry-After`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The account service runs the previous release":{"description":"1. The account service runs the previous release","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/authenticators"}}}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/account/authenticators/{authenticatorId}":{"delete":{"tags":["Account Security"],"summary":"Remove one of your authenticators","description":"Removes one of the person's passkeys or the authenticator app. A removed passkey signs in nowhere\nfrom the answer on.\n\n**Never a lock-out.** The removal is refused with <code>409\nauthenticator-removal-would-lock-out</code> when it would leave a membership the person can enter\ntoday without any authenticator its authentication policy accepts, or, for a platform identity, the\nplatform plane without a passkey. The refusal names no tenant, and nothing is removed: register\nanother authenticator first.\n\n**The platform keeps an administrator.** A PLATFORM_SUPER_ADMIN's removal is refused with <code>409\nlast-platform-super-admin-would-lock-out</code> when no PLATFORM_SUPER_ADMIN could sign in after it:\nanother active platform member needs PLATFORM_SUPER_ADMIN and a way to sign in first. The break-glass\nentry does not count. Nothing is removed.\n\nAn id that is not one of the person's own authenticators is answered <code>404</code>, exactly as an\nunknown one.\n\nSensitive: <code>x-ankasecure-fresh-authentication: required</code>.\n","operationId":"deleteAccountAuthenticator","parameters":[{"name":"authenticatorId","in":"path","description":"The opaque authenticator id `listAccountAuthenticators` returns; never a WebAuthn credential id","required":true,"schema":{"type":"string","format":"uuid"},"example":"7c1e4b2a-9d3f-4e6a-8b5c-2f1d0e9a7b64"}],"responses":{"204":{"description":"The authenticator is removed."},"400":{"description":"`invalid-input`: `authenticatorId` is not a UUID.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Malformed authenticator identifier":{"description":"1. Malformed authenticator identifier","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Parameter 'authenticatorId' is not valid","instance":"/api/v3/auth/account/authenticators/not-an-authenticator","timestamp":1765057500}}}}}},"401":{"description":"Two causes, told apart by the RFC 9457 `type`. No access token was presented, or the one presented is expired, revoked or otherwise not accepted. Or (`reauthentication-required`) the person's last authentication is older than the reauthentication window (`auth.reauthentication.max-age-seconds`, 300 seconds by default): the answer carries `WWW-Authenticate: Bearer error=\"insufficient_user_authentication\", error_description=\"...\", max_age=\"<seconds>\"` and the window in `extensions.maxAge`; nothing is read or changed, and the person reauthenticates (`max_age` on the `refresh_token` grant) and retries. An impersonation token is refused with `403` instead, and tokens of applications and actors are never refused for it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The last authentication is older than the window":{"description":"1. The last authentication is older than the window","value":{"type":"https://docs.ankatech.co/errors/reauthentication-required","title":"Reauthentication Required","status":401,"detail":"This operation requires a more recent authentication. Reauthenticate and retry the request; nothing was changed.","instance":"/api/v3/auth/account/recovery-codes","timestamp":1765057500,"extensions":{"maxAge":300}}}}}}},"403":{"description":"Two causes, told apart by the RFC 9457 `type`. `impersonation-operation-forbidden`: the access token is an impersonation token; the person it names is the tenant's own user, so the operation is refused before anything is read or changed. `forbidden`: the access token is not a person's session (it records no sign-in method).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/authenticators","timestamp":1765057500}}}}}},"404":{"description":"The person holds no authenticator with this id. Nothing changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Not one of the person's authenticators":{"description":"1. Not one of the person's authenticators","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"Authenticator not found","instance":"/api/v3/auth/account/authenticators/7c1e4b2a-9d3f-4e6a-8b5c-2f1d0e9a7b64","timestamp":1765057500}}}}}},"409":{"description":"`authenticator-removal-would-lock-out`: the removal would leave a membership, or the platform plane, without a way to sign in. `last-platform-super-admin-would-lock-out`: no PLATFORM_SUPER_ADMIN could sign in after the removal. Nothing is removed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The last passkey a tenant's policy accepts":{"description":"1. The last passkey a tenant's policy accepts","value":{"type":"https://docs.ankatech.co/errors/authenticator-removal-would-lock-out","title":"Authenticator Removal Would Lock Out","status":409,"detail":"Removing this authenticator would leave no way to sign in to one of your memberships. Add another authenticator first.","instance":"/api/v3/auth/account/authenticators/7c1e4b2a-9d3f-4e6a-8b5c-2f1d0e9a7b64","timestamp":1765057500}},"2. The last way in of the platform's only super administrator":{"description":"2. The last way in of the platform's only super administrator","value":{"type":"https://docs.ankatech.co/errors/last-platform-super-admin-would-lock-out","title":"Last Platform Super Administrator","status":409,"detail":"This would leave the platform without a PLATFORM_SUPER_ADMIN who can sign in. Give another active platform member PLATFORM_SUPER_ADMIN and a way to sign in first; nothing was changed.","instance":"/api/v3/auth/account/authenticators/7c1e4b2a-9d3f-4e6a-8b5c-2f1d0e9a7b64","timestamp":1765057500}}}}}},"502":{"description":"The account service could not be reached, or answered outside the contract of this operation. Nothing is reported as changed; repeat the request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"504":{"description":"The account service did not answer within this operation's deadline. Repeat the request; a change that completed meanwhile is visible in the list.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready`: the account service does not serve this operation yet (a platform being upgraded runs the previous release of it for a while). Nothing changed; repeat the request after `Retry-After`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The account service runs the previous release":{"description":"1. The account service runs the previous release","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/authenticators"}}}}}}},"security":[{"bearerAuth":[]}],"x-ankasecure-fresh-authentication":"required"},"patch":{"tags":["Account Security"],"summary":"Rename one of your passkeys","description":"Gives one of the person's passkeys a new name and answers its entry as\n<code>listAccountAuthenticators</code> lists it. An authenticator app has no name and is refused\nwith <code>422</code>.\n\nAn id that is not one of the person's own authenticators is answered <code>404</code>, exactly as an\nunknown one, so the operation never confirms another person's authenticator.\n\nSensitive: <code>x-ankasecure-fresh-authentication: required</code>.\n","operationId":"updateAccountAuthenticator","parameters":[{"name":"authenticatorId","in":"path","description":"The opaque authenticator id `listAccountAuthenticators` returns; never a WebAuthn credential id","required":true,"schema":{"type":"string","format":"uuid"},"example":"2b9d4e61-0c7a-4f3e-9a18-5d6c7b8a9e01"}],"requestBody":{"description":"The new name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountAuthenticatorUpdateRequest"},"examples":{"1. A descriptive name":{"description":"1. A descriptive name","value":{"displayName":"Security key in the office safe"}}}}},"required":true},"responses":{"200":{"description":"The passkey after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountAuthenticatorResponse"},"examples":{"1. A renamed security key":{"description":"1. A renamed security key","value":{"authenticatorId":"2b9d4e61-0c7a-4f3e-9a18-5d6c7b8a9e01","kind":"PASSKEY","displayName":"Security key in the office safe","createdAt":"2026-10-14T16:40:27Z","lastUsedAt":null,"properties":{"phishingResistant":true,"userVerifying":true,"syncable":false,"deviceBound":true,"hardwareBacked":true,"attested":true,"discoverable":true}}}}}}},"400":{"description":"`invalid-input`: `authenticatorId` is not a UUID. `validation`: the name is missing, longer than 100 characters or carries a control character.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Malformed authenticator identifier":{"description":"1. Malformed authenticator identifier","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Parameter 'authenticatorId' is not valid","instance":"/api/v3/auth/account/authenticators/not-an-authenticator","timestamp":1765057500}}}}}},"401":{"description":"Two causes, told apart by the RFC 9457 `type`. No access token was presented, or the one presented is expired, revoked or otherwise not accepted. Or (`reauthentication-required`) the person's last authentication is older than the reauthentication window (`auth.reauthentication.max-age-seconds`, 300 seconds by default): the answer carries `WWW-Authenticate: Bearer error=\"insufficient_user_authentication\", error_description=\"...\", max_age=\"<seconds>\"` and the window in `extensions.maxAge`; nothing is read or changed, and the person reauthenticates (`max_age` on the `refresh_token` grant) and retries. An impersonation token is refused with `403` instead, and tokens of applications and actors are never refused for it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The last authentication is older than the window":{"description":"1. The last authentication is older than the window","value":{"type":"https://docs.ankatech.co/errors/reauthentication-required","title":"Reauthentication Required","status":401,"detail":"This operation requires a more recent authentication. Reauthenticate and retry the request; nothing was changed.","instance":"/api/v3/auth/account/recovery-codes","timestamp":1765057500,"extensions":{"maxAge":300}}}}}}},"403":{"description":"Two causes, told apart by the RFC 9457 `type`. `impersonation-operation-forbidden`: the access token is an impersonation token; the person it names is the tenant's own user, so the operation is refused before anything is read or changed. `forbidden`: the access token is not a person's session (it records no sign-in method).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/authenticators","timestamp":1765057500}}}}}},"404":{"description":"The person holds no authenticator with this id. Nothing changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Not one of the person's authenticators":{"description":"1. Not one of the person's authenticators","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"Authenticator not found","instance":"/api/v3/auth/account/authenticators/7c1e4b2a-9d3f-4e6a-8b5c-2f1d0e9a7b64","timestamp":1765057500}}}}}},"422":{"description":"The id names the person's authenticator app, which has no name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. An authenticator app":{"description":"1. An authenticator app","value":{"type":"https://docs.ankatech.co/errors/unprocessable-entity","title":"Unprocessable Entity","status":422,"detail":"A TOTP authenticator has no name to change.","instance":"/api/v3/auth/account/authenticators/e4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8","timestamp":1765057500}}}}}},"502":{"description":"The account service could not be reached, or answered outside the contract of this operation. Nothing is reported as changed; repeat the request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"504":{"description":"The account service did not answer within this operation's deadline. Repeat the request; a change that completed meanwhile is visible in the list.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready`: the account service does not serve this operation yet (a platform being upgraded runs the previous release of it for a while). Nothing changed; repeat the request after `Retry-After`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The account service runs the previous release":{"description":"1. The account service runs the previous release","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/authenticators"}}}}}},"413":{"description":"Payload Too Large. The JSON request body exceeds this service's limit of 262144 bytes. A request whose Content-Length is over the limit is refused before its body is read; a body sent without one is refused when the read crosses the limit. Authentication and authorization are decided first, so an unauthenticated request is answered 401 whatever its size. The body names only the limit, never the size that was sent.\n","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"JSON body over the service's limit":{"summary":"A request body larger than the limit, refused before it reached the operation","value":{"type":"https://docs.ankatech.co/errors/payload-too-large","title":"Payload Too Large","status":413,"detail":"Request body size exceeds the maximum allowed limit of 262144 bytes.","instance":"/api/v3/auth/account/authenticators/3f2a9c1e-5b7d-4e8a-9f10-2c3d4e5f6a7b","timestamp":1765057500}}}}}}},"security":[{"bearerAuth":[]}],"x-ankasecure-fresh-authentication":"required"}},"/api/v3/auth/token/s/blacklist":{"get":{"tags":["Token Management"],"summary":"Admin: View revoked tokens","description":"View currently blacklisted (revoked) tokens.\n\n**Authorization model:**\n- `platform.token.read`: returns the global blacklist, or one tenant's with `?tenantId=`.\n  Requires a caller of the platform tenant on PRIVATE_CLOUD or ON_PREMISE — rejected in SaaS.\n- `auth.tenant.token.read`: returns the blacklist of the caller's own tenant. A `?tenantId=`\n  naming another tenant is refused (403).\n- `?tenantId=` names a tenant only as a UUID in its canonical lower-case form\n  (`8-4-4-4-12`). Any other spelling of a tenant — upper case, surrounding spaces, a\n  shortened form such as `0-0-0-0-1` — names no tenant and is refused (403), like a tenant\n  the caller does not reach.\n- Both scopes: whatever either covers; with no `?tenantId=`, the global blacklist where the\n  platform scope reaches every tenant, otherwise the caller's own tenant.\n\n**Callers:** a person's access token holding one of the two scopes, or an\nadministrative workload's token under `Authorization: DPoP` with a proof for this\nrequest (RFC 9449). A workload reads the tenant its token names, and only a\nplatform-tenant workload may hold `platform.token.read`.\n\nResults are limited to 5000 entries per call. Truncation is indicated by\n`\"truncated\": true` in the response.\n","operationId":"getBlacklist","parameters":[{"name":"tenantId","in":"query","description":"The tenant whose blacklist is listed, as a tenant UUID in its canonical lower-case form. Omitted: every tenant on the platform plane, otherwise the caller's own tenant.","required":false,"schema":{"type":"string"},"example":"7c9e6679-7425-40de-944b-e07fc1f90ae7"}],"responses":{"200":{"description":"List of blacklisted token JTIs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlacklistResponse"}}}},"401":{"description":"No access token was presented, or the one presented is not accepted. A person's token is challenged with `WWW-Authenticate: Bearer`. An administrative workload's token is accepted only under `Authorization: DPoP` with a proof bound to the token and to this request (RFC 9449); otherwise the challenge is `WWW-Authenticate: DPoP error=\"invalid_token\"` (the token was presented under `Bearer`, or is not bound to a key) or `WWW-Authenticate: DPoP error=\"invalid_dpop_proof\"` (the proof is missing, malformed, already used, or names another method or URL). The body is the same in every case.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Token missing, rejected, or a workload token without a valid DPoP proof":{"description":"1. Token missing, rejected, or a workload token without a valid DPoP proof","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication is required to call this endpoint.","instance":"/api/v3/auth/token/s/3f2b8c1e-9d4a-4e7b-a5c6-1b2d3e4f5a6b","timestamp":1765057500,"extensions":{"code":"UNAUTHENTICATED"}}}}}}},"403":{"description":"The caller holds neither read scope; or it holds only platform.token.read on a SaaS deployment or from outside the platform tenant; or `tenantId` names a tenant the caller does not reach, or is not a tenant UUID in its canonical lower-case form. An administrative workload outside the platform tenant that holds a `platform.token.*` scope is refused, whatever the operation; a tenant workload acts with `auth.tenant.token.*` in the tenant its token names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready` with `Retry-After`: the store that records each DPoP proof once could not decide whether this proof was already used, so the workload's request was neither admitted nor refused as a bad credential, and nothing changed.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"5"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The DPoP proof could not be checked for replay":{"description":"1. The DPoP proof could not be checked for replay","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/token/introspect"}}}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/tenant/users/{userId}/sessions":{"get":{"tags":["Tenant User Sessions"],"summary":"List a member's sessions in your tenant","description":"Lists the live sessions of a member whose current tenant is the tenant of the caller's access\ntoken, newest first. The member's sessions in other tenants are not listed. A tenant id sent in\na header, the query or a body is ignored: the tenant is always the token's.\n\nThe member is identified by their identity id. An identity that holds no active membership of\nthe tenant is answered <code>404</code>, whatever sessions it holds elsewhere.\n\nA session shows who authenticated it (<code>authority</code>, <code>idpKind</code>) only when that\nis a local sign-in or an identity provider of this tenant: its own provider, or the deployment's\nprovider the tenant inherits. A session federated through another tenant's provider that then\nswitched into this tenant shows both as null; the other tenant's provider is never disclosed.\n\nThe answer is never stored by a cache (<code>Cache-Control: no-store</code>).\n","operationId":"listTenantUserSessions","parameters":[{"name":"userId","in":"path","description":"The member's identity identifier: the user id the tenant's user list shows.","required":true,"schema":{"type":"string","format":"uuid"},"example":"7c9e6679-7425-40de-944b-e07fc1f90ae7"}],"responses":{"200":{"description":"The member's live sessions in the caller's tenant. An empty list means there is none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HumanSessionsResponse"},"examples":{"1. A member signed in to the tenant with a password":{"description":"1. A member signed in to the tenant with a password","value":{"sessions":[{"sessionId":"9b8a7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","tenantId":"00000000-0000-0000-0000-000000000010","authTime":"2026-10-16T07:30:02Z","authority":"LOCAL","idpKind":null,"methods":["pwd"],"createdAt":"2026-10-16T07:30:02Z","lastActivityAt":"2026-10-16T09:41:17Z","absoluteExpiresAt":"2026-10-16T19:30:02Z","rememberMe":false,"clientAddress":"192.0.2.45","userAgent":"Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0","current":false}]}},"2. A member with no session in the tenant":{"description":"2. A member with no session in the tenant","value":{"sessions":[]}}}}}},"400":{"description":"`userId` is not a UUID.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"No access token was presented, or the one presented is not accepted. A person's token is challenged with `WWW-Authenticate: Bearer`. An administrative workload's token is accepted only under `Authorization: DPoP` with a proof bound to the token and to this request (RFC 9449); otherwise the challenge is `WWW-Authenticate: DPoP error=\"invalid_token\"` (the token was presented under `Bearer`, or is not bound to a key) or `WWW-Authenticate: DPoP error=\"invalid_dpop_proof\"` (the proof is missing, malformed, already used, or names another method or URL). The body is the same in every case.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"The access token does not carry `auth.tenant.token.read`. An administrative workload outside the platform tenant that holds a `platform.token.*` scope is refused, whatever the operation; a tenant workload acts with `auth.tenant.token.*` in the tenant its token names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Missing scope":{"description":"1. Missing scope","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"You do not have permission to access this resource.","instance":"/api/v3/auth/tenant/users/7c9e6679-7425-40de-944b-e07fc1f90ae7/sessions","timestamp":1765057500}}}}}},"404":{"description":"The identity holds no active membership of the caller's tenant.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Not a member of this tenant":{"description":"1. Not a member of this tenant","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"No member with this identifier in this tenant","instance":"/api/v3/auth/tenant/users/7c9e6679-7425-40de-944b-e07fc1f90ae7/sessions","timestamp":1765057500}}}}}},"502":{"description":"The account service that confirms the membership could not be reached, or answered outside its contract.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready`: the session store could not be read or written, and nothing changed; or, for a revocation, its audit record could not be confirmed after the sessions were ended, which stands; or an administrative workload's DPoP proof could not be checked for replay, and nothing was read or changed. Retry after `Retry-After`.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"5"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Session store unavailable":{"description":"1. Session store unavailable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/sessions/revoke-others"}}}}}},"504":{"description":"The account service that confirms the membership did not answer within the deadline.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/platform/identities/{identityId}/sessions":{"get":{"tags":["Platform Identity Sessions"],"summary":"List an identity's sessions","description":"Lists the live sessions of an identity in every tenant, newest first. An identity with no live\nsession, or an identifier that names no identity, is an empty list.\n\nThe caller must be signed in to the platform tenant: any other caller is refused with\n<code>403</code> before anything is read. Not available on a SaaS deployment.\n\nThe answer is never stored by a cache (<code>Cache-Control: no-store</code>).\n","operationId":"listPlatformIdentitySessions","parameters":[{"name":"identityId","in":"path","description":"The identity's identifier: the subject (`sub`) of the person's access tokens.","required":true,"schema":{"type":"string","format":"uuid"},"example":"7c9e6679-7425-40de-944b-e07fc1f90ae7"}],"responses":{"200":{"description":"The identity's live sessions. An empty list means there is none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HumanSessionsResponse"},"examples":{"1. An identity signed in to two tenants":{"description":"1. An identity signed in to two tenants","value":{"sessions":[{"sessionId":"4f1d2c3b-9a8e-4d7c-b6a5-0e1f2a3b4c5d","tenantId":"00000000-0000-0000-0000-000000000010","authTime":"2026-10-16T08:12:41Z","authority":"LOCAL","idpKind":null,"methods":["pwd"],"createdAt":"2026-10-16T08:12:41Z","lastActivityAt":"2026-10-16T09:40:05Z","absoluteExpiresAt":"2026-10-16T20:12:41Z","rememberMe":false,"clientAddress":"203.0.113.24","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36","current":true},{"sessionId":"9b8a7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","tenantId":"00000000-0000-0000-0000-000000000011","authTime":"2026-10-15T22:03:10Z","authority":"3f1c2a9e-4b7d-4e2a-9c11-0a2b3c4d5e6f","idpKind":"OIDC","methods":["pwd","mfa"],"createdAt":"2026-10-15T22:03:10Z","lastActivityAt":"2026-10-16T07:55:48Z","absoluteExpiresAt":"2026-10-16T10:03:10Z","rememberMe":true,"clientAddress":"198.51.100.7","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15","current":false}]}},"2. No live session":{"description":"2. No live session","value":{"sessions":[]}}}}}},"400":{"description":"`identityId` is not a UUID.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"No access token was presented, or the one presented is not accepted. A person's token is challenged with `WWW-Authenticate: Bearer`. An administrative workload's token is accepted only under `Authorization: DPoP` with a proof bound to the token and to this request (RFC 9449); otherwise the challenge is `WWW-Authenticate: DPoP error=\"invalid_token\"` (the token was presented under `Bearer`, or is not bound to a key) or `WWW-Authenticate: DPoP error=\"invalid_dpop_proof\"` (the proof is missing, malformed, already used, or names another method or URL). The body is the same in every case.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"`forbidden`: the access token does not carry the scope, or was not issued for the platform tenant, in which case nothing is read; or the deployment is SaaS. An administrative workload outside the platform tenant that holds a `platform.token.*` scope is refused, whatever the operation; a tenant workload acts with `auth.tenant.token.*` in the tenant its token names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Not the platform tenant, or missing scope":{"description":"1. Not the platform tenant, or missing scope","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"You do not have permission to access this resource.","instance":"/api/v3/auth/tenant/users/7c9e6679-7425-40de-944b-e07fc1f90ae7/sessions","timestamp":1765057500}},"2. SaaS deployment":{"description":"2. SaaS deployment","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Deployment Mode Restriction","status":403,"detail":"This operation is only available in PRIVATE_CLOUD or ON_PREMISE deployments.","instance":"/api/v3/auth/platform/identities/7c9e6679-7425-40de-944b-e07fc1f90ae7/sessions","timestamp":1765057500}}}}}},"503":{"description":"`/problems/dependency-not-ready`: the session store could not be read or written, and nothing changed; or, for a revocation, its audit record could not be confirmed after the sessions were ended, which stands; or an administrative workload's DPoP proof could not be checked for replay, and nothing was read or changed. Retry after `Retry-After`.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"5"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Session store unavailable":{"description":"1. Session store unavailable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/sessions/revoke-others"}}}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/account/sessions":{"get":{"tags":["Account Sessions"],"summary":"List your sessions","description":"Lists the live sessions of the person the access token names, in every tenant, newest first.\nThe session this request comes from is marked <code>current: true</code>.\n\n**What is listed.** Every session that has not ended and has not passed its absolute expiry.\nA session ends when it is revoked or logged out, and when a refresh is presented after its\ninactivity window; until then it is listed. Each entry names the tenant the session is in now,\nhow and when the person authenticated (<code>authority</code>, <code>idpKind</code>,\n<code>methods</code> as RFC 8176 values, <code>authTime</code>), its activity and its absolute\nexpiry, and the address and user agent of the sign-in. No token is shown.\n\n**Your own sessions only.** The request carries no identifier: the person is the one the access\ntoken names. A session acting on behalf of a tenant (impersonation) is refused with\n<code>403</code> before anything is read. An access token that is not a person's session (for\nexample a cryptographic actor's) is refused with <code>403</code>.\n\nThe answer is never stored by a cache (<code>Cache-Control: no-store</code>).\n","operationId":"listAccountSessions","responses":{"200":{"description":"The person's live sessions. An empty list means there is none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HumanSessionsResponse"},"examples":{"1. A password session (current) and a federated Remember Me session":{"description":"1. A password session (current) and a federated Remember Me session","value":{"sessions":[{"sessionId":"4f1d2c3b-9a8e-4d7c-b6a5-0e1f2a3b4c5d","tenantId":"00000000-0000-0000-0000-000000000010","authTime":"2026-10-16T08:12:41Z","authority":"LOCAL","idpKind":null,"methods":["pwd"],"createdAt":"2026-10-16T08:12:41Z","lastActivityAt":"2026-10-16T09:40:05Z","absoluteExpiresAt":"2026-10-16T20:12:41Z","rememberMe":false,"clientAddress":"203.0.113.24","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36","current":true},{"sessionId":"9b8a7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","tenantId":"00000000-0000-0000-0000-000000000011","authTime":"2026-10-15T22:03:10Z","authority":"3f1c2a9e-4b7d-4e2a-9c11-0a2b3c4d5e6f","idpKind":"OIDC","methods":["pwd","mfa"],"createdAt":"2026-10-15T22:03:10Z","lastActivityAt":"2026-10-16T07:55:48Z","absoluteExpiresAt":"2026-10-16T10:03:10Z","rememberMe":true,"clientAddress":"198.51.100.7","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15","current":false}]}},"2. No live session":{"description":"2. No live session","value":{"sessions":[]}}}}}},"401":{"description":"No access token was presented, or the one presented is expired, revoked or otherwise not accepted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. No credential presented":{"description":"1. No credential presented","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication is required to call this endpoint.","instance":"/api/v3/auth/account/sessions","timestamp":1765057500}}}}}},"403":{"description":"Two causes, told apart by the RFC 7807 `type`. `impersonation-operation-forbidden`: the access token is an impersonation token, and nothing is read. `forbidden`: the access token is not a person's session.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/sessions","timestamp":1765057500}},"2. Not a person's session":{"description":"2. Not a person's session","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"You do not have permission to access this resource.","instance":"/api/v3/auth/tenant/users/7c9e6679-7425-40de-944b-e07fc1f90ae7/sessions","timestamp":1765057500}}}}}},"503":{"description":"`/problems/dependency-not-ready`: the session store could not be read or written, and nothing changed; or, for a revocation, its audit record could not be confirmed after the sessions were ended, which stands; or an administrative workload's DPoP proof could not be checked for replay, and nothing was read or changed. Retry after `Retry-After`.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"5"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Session store unavailable":{"description":"1. Session store unavailable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/sessions/revoke-others"}}}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/account/memberships":{"get":{"tags":["Account Lifecycle"],"summary":"List the tenants you may switch to","description":"Lists the tenants the person named by the access token may switch to, for a tenant switcher.\nA person is one identity with a membership in each tenant they belong to; switching to one of\nthe listed tenants is a <code>refresh_token</code> grant carrying that <code>tenant_id</code>\non the token endpoint, with no password.\n\n**What is listed.** The person's ACTIVE memberships of tenants whose state permits sign-in\n(each with the tenant's id, its name and the account type), ordered by tenant id, including\nthe tenant this session is signed in to. Pending invitations, suspended memberships and\ntenants that are suspended or closed are not listed.\n\n**Single sign-on policy.** A tenant that admits a member only through its identity provider\nlists that membership only for a session signed in through that provider. A session signed in\nwith a password, or through another provider, does not see it: it could not switch into it.\nThe session's sign-in method is the one recorded in its access token when it was issued.\n\n**Your own memberships only.** The request carries no identifier: the person is the one the\naccess token names. A session acting on behalf of a tenant (impersonation) is refused with\n<code>403</code> before anything is read — the token there names the tenant's own user, and\nlisting that person's other tenants to a support session would disclose them. An access token\nthat is not a person's session (for example a cryptographic actor's) is refused with\n<code>403</code>.\n\nThe answer is never stored by a cache (<code>Cache-Control: no-store</code>).\n","operationId":"listAccountMemberships","responses":{"200":{"description":"The tenants the person may switch to. An empty list means there is none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountMembershipsResponse"},"examples":{"1. A person in two tenants, signed in with a password":{"description":"1. A person in two tenants, signed in with a password","value":{"memberships":[{"accountType":"TENANT_ADMIN","tenantId":"00000000-0000-0000-0000-000000000010","tenantName":"BigBank Retail"},{"accountType":"TENANT_ADMIN","tenantId":"00000000-0000-0000-0000-000000000011","tenantName":"BigBank Corporate"}]}},"2. Signed in through a tenant's identity provider: its single sign-on membership is listed":{"description":"2. Signed in through a tenant's identity provider: its single sign-on membership is listed","value":{"memberships":[{"accountType":"TENANT_ADMIN","tenantId":"00000000-0000-0000-0000-000000000011","tenantName":"BigBank Corporate"}]}},"3. No tenant to switch to":{"description":"3. No tenant to switch to","value":{"memberships":[]}}}}}},"401":{"description":"No access token was presented, or the one presented is expired, revoked or otherwise not accepted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. No credential presented":{"description":"1. No credential presented","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication is required to call this endpoint.","instance":"/api/v3/auth/account/memberships","timestamp":1765057500}}}}}},"403":{"description":"Two causes, told apart by the RFC 7807 `type`. `impersonation-operation-forbidden`: the access token is an impersonation token — the caller is acting on behalf of a tenant, and the person the token names is the tenant's own user, whose other tenants are not disclosed to a support session; nothing is read. `forbidden`: the access token is not a person's session (it records no sign-in method, or names no identity), for example a cryptographic actor's token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/memberships","timestamp":1765057500}},"2. Not a person's session":{"description":"2. Not a person's session","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"You do not have permission to access this resource.","instance":"/api/v3/auth/account/memberships","timestamp":1765057500}}}}}},"502":{"description":"The account service could not be reached, or answered outside the contract of this operation. Reported as an outage of ours — never as an empty list, which would tell a person who has tenants that they have none.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"504":{"description":"The account service did not answer within this operation's deadline.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/api/v3/auth/account/authenticators":{"get":{"tags":["Account Security"],"summary":"List your authenticators","description":"Lists the passkeys and the authenticator app (TOTP) of the person the access token names: the\npasskeys first, then the TOTP factor, each oldest first.\n\n**No strength ranking.** An authentication policy names the capability properties it requires,\nand each entry states its own: <code>phishingResistant</code> (every passkey: its signature is\nbound to this site's origin, W3C WebAuthn Level 3 §13.4.1; never TOTP), <code>userVerifying</code>\n(the UV flag: a PIN or biometric on every use), <code>syncable</code> and <code>deviceBound</code>\n(the backup-eligibility flag BE, as the authenticator reports it), <code>hardwareBacked</code> and\n<code>attested</code> (true only when an attestation verified against the FIDO Metadata Service\nBLOB loaded in this deployment), and <code>discoverable</code> (the <code>credProps</code>\nextension, <code>null</code> when the browser did not report it). A property is\n<code>true</code> only when it is established. An authenticator app establishes none of them.\n\n**Nothing secret.** No entry carries a WebAuthn credential id, a public key, an attestation\nstatement or a TOTP secret. <code>authenticatorId</code> is an opaque id assigned by the platform:\nthe value the rename and the removal take in their path.\n\n**The password.** <code>passwordHeld</code> states whether the person holds a password, the one\n<code>deleteAccountPassword</code> removes. It is absent when the account service does not state it\n(a platform being upgraded runs the previous release of it for a while): absence never means\n<code>false</code>.\n\n**Adding an authenticator app.** <code>totpEnrolmentAdmitted</code> states whether the person's\naccount admits an authenticator app at all: <code>false</code> when no tenant the person enters with\ntheir own authenticators, nor the platform for a platform administrator, admits password + TOTP. It\ndoes not say whether one is already held (the list\ndoes): a second enrolment is refused with <code>409</code>. It is absent when the account service does\nnot state it, as above: absence never means <code>false</code>.\n\n**Adding a passkey.** <code>passkeyEnrolmentAdmitted</code> states whether a passkey would serve a\nsign-in of the person: <code>true</code> when a tenant they enter with their own authenticators, or\nthe platform for a platform administrator, accepts a passkey; <code>false</code> when every tenant of\nthe person is entered through its identity provider only. It is absent when the account service does\nnot state it, as above: absence never means <code>false</code>.\n\nThe answer is never stored by a cache (<code>Cache-Control: no-store</code>).\n","operationId":"listAccountAuthenticators","responses":{"200":{"description":"The person's authenticators. An empty list means the person holds none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountAuthenticatorsResponse"},"examples":{"1. A synced passkey, an attested security key and an authenticator app":{"description":"1. A synced passkey, an attested security key and an authenticator app","value":{"authenticators":[{"authenticatorId":"7c1e4b2a-9d3f-4e6a-8b5c-2f1d0e9a7b64","kind":"PASSKEY","displayName":"Work laptop","createdAt":"2026-10-12T09:14:03Z","lastUsedAt":"2026-10-17T15:02:41Z","properties":{"phishingResistant":true,"userVerifying":true,"syncable":true,"deviceBound":false,"hardwareBacked":false,"attested":false,"discoverable":true},"rpId":"dev.ankatech.co","registeredForCurrentRpId":true},{"authenticatorId":"2b9d4e61-0c7a-4f3e-9a18-5d6c7b8a9e01","kind":"PASSKEY","displayName":"Security key in the office safe","createdAt":"2026-10-14T16:40:27Z","lastUsedAt":null,"properties":{"phishingResistant":true,"userVerifying":true,"syncable":false,"deviceBound":true,"hardwareBacked":true,"attested":true,"discoverable":true},"rpId":"dev.ankatech.co","registeredForCurrentRpId":true},{"authenticatorId":"e4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8","kind":"TOTP","displayName":null,"createdAt":"2026-09-30T11:05:12Z","lastUsedAt":"2026-10-16T08:12:41Z","properties":{"phishingResistant":false,"userVerifying":false,"syncable":false,"deviceBound":false,"hardwareBacked":false,"attested":false,"discoverable":null}}],"passwordHeld":false,"totpEnrolmentAdmitted":true,"passkeyEnrolmentAdmitted":true}},"2. A person who signs in with a password only":{"description":"2. A person who signs in with a password only","value":{"authenticators":[],"passwordHeld":true,"totpEnrolmentAdmitted":true,"passkeyEnrolmentAdmitted":true}},"3. A platform administrator: passkeys only, no authenticator app admitted":{"description":"3. A platform administrator: passkeys only, no authenticator app admitted","value":{"authenticators":[{"authenticatorId":"5a8c2e17-3b4d-4f60-9e71-0d2c3b4a5f68","kind":"PASSKEY","displayName":"Operations security key","createdAt":"2026-10-02T10:31:55Z","lastUsedAt":"2026-10-19T07:48:12Z","properties":{"phishingResistant":true,"userVerifying":true,"syncable":false,"deviceBound":true,"hardwareBacked":true,"attested":true,"discoverable":true}}],"passwordHeld":false,"totpEnrolmentAdmitted":false,"passkeyEnrolmentAdmitted":true}}}}}},"401":{"description":"No access token was presented, or the one presented is expired, revoked or otherwise not accepted. Nothing is read or changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. No credential presented":{"description":"1. No credential presented","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication is required to call this endpoint.","instance":"/api/v3/auth/account/authenticators","timestamp":1765057500}}}}}},"403":{"description":"Two causes, told apart by the RFC 9457 `type`. `impersonation-operation-forbidden`: the access token is an impersonation token; the person it names is the tenant's own user, so the operation is refused before anything is read or changed. `forbidden`: the access token is not a person's session (it records no sign-in method).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/authenticators","timestamp":1765057500}},"2. Not a person's session":{"description":"2. Not a person's session","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"You do not have permission to access this resource.","instance":"/api/v3/auth/account/authenticators","timestamp":1765057500}}}}}},"404":{"description":"The identity the access token names no longer exists.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"502":{"description":"The account service could not be reached, or answered outside the contract of this operation. Nothing is reported as changed; repeat the request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Account service unavailable":{"description":"1. Account service unavailable","value":{"type":"https://docs.ankatech.co/errors/upstream-unavailable","title":"Upstream Unavailable","status":502,"detail":"The account service is temporarily unavailable.","instance":"/api/v3/auth/account/authenticators"}}}}}},"504":{"description":"The account service did not answer within this operation's deadline. Repeat the request; a change that completed meanwhile is visible in the list.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready`: the account service does not serve this operation yet (a platform being upgraded runs the previous release of it for a while). Nothing changed; repeat the request after `Retry-After`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The account service runs the previous release":{"description":"1. The account service runs the previous release","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/authenticators"}}}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/.well-known/oauth-authorization-server":{"get":{"tags":["Authorization Server Metadata"],"summary":"Get the authorization server metadata","description":"Returns this service's OAuth 2.0 authorization server metadata\n(<a href=\"https://www.rfc-editor.org/rfc/rfc8414#section-2\" target=\"_blank\">RFC 8414 §2</a>).\n\n<ul>\n  <li><code>issuer</code> — the <code>iss</code> claim of every access token this service\n      issues. It is the platform's issuer identifier, the same in every deployment, and it is\n      not the URL this document is served from: compare a token's <code>iss</code> with this\n      value and read the document by its location.</li>\n  <li><code>token_endpoint</code> — the public URL of the token endpoint. A DPoP proof sent\n      with a token request names exactly this URL as its <code>htu</code>.</li>\n  <li><code>jwks_uri</code> — the public URL of the JWK Set that verifies the tokens.</li>\n  <li><code>dpop_signing_alg_values_supported</code>\n      (<a href=\"https://www.rfc-editor.org/rfc/rfc9449#section-5.1\" target=\"_blank\">RFC 9449 §5.1</a>)\n      — the algorithms a DPoP proof may use when an administrative workload requests a\n      DPoP-bound token.</li>\n</ul>\n\n<p><b>Authentication.</b> None. Credentials on the request are ignored.</p>\n","operationId":"getAuthorizationServerMetadata","responses":{"200":{"description":"The metadata document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizationServerMetadata"},"examples":{"Authorization server metadata":{"summary":"The document served on the dev environment","description":"Authorization server metadata","value":{"issuer":"https://auth.ankatech.co","token_endpoint":"https://dev.ankatech.co/api/v3/auth/token","jwks_uri":"https://dev.ankatech.co/api/v3/auth/.well-known/jwks.json","dpop_signing_alg_values_supported":["ES256","ES384","PS256","RS256"]}}}}}},"406":{"description":"The `Accept` header does not admit `application/json`. Status line only: no body and no `Content-Type` (RFC 9110 §15.5.7).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizationServerMetadata"}}}}},"security":[]}},"/api/v3/auth/.well-known/jwks.json":{"get":{"tags":["Token Signing Keys"],"summary":"Get the token signing JWK Set","description":"Returns the public keys that verify the access tokens this service issues, as a JWK Set\n(<a href=\"https://www.rfc-editor.org/rfc/rfc7517#section-5\" target=\"_blank\">RFC 7517 §5</a>).\n\n<p>Every access token carries a <code>kid</code> header naming the key that signed it: the\nkey's RFC 7638 SHA-256 thumbprint. A verifier selects the member of this set with that\n<code>kid</code> and verifies the signature with it; a token without a <code>kid</code>, or\nnaming a key absent from the set, is refused.</p>\n\n<p>The set holds three kinds of key, all <code>kty=RSA</code>, <code>use=sig</code>,\n<code>alg=RS256</code>, RSA-3072:</p>\n<ul>\n  <li>the key that signs today;</li>\n  <li>the key that will sign next, published before it signs its first token so every\n      verifier already holds it;</li>\n  <li>keys that signed until a recent rotation, kept until the last token they signed\n      has expired.</li>\n</ul>\n<p>The set never carries private key material.</p>\n\n<p><b>Caching.</b> The response declares <code>Cache-Control: public, max-age=300</code>.\nA verifier refetches when the cache expires, or at most once per rate-limit window when a\ntoken names a <code>kid</code> it does not hold. <code>HEAD</code> is supported.</p>\n\n<p><b>Representation.</b> The same body under two media types.\n<code>application/jwk-set+json</code>\n(<a href=\"https://www.rfc-editor.org/rfc/rfc7517#section-8.5\" target=\"_blank\">RFC 7517 §8.5</a>)\nis the default, served for that type, a wildcard, or no <code>Accept</code>.\n<code>application/json</code> is served when the client asks for it, as the common identity\nproviders publish their key sets. Any other <code>Accept</code> is answered <code>406</code>.</p>\n\n<p><b>Authentication.</b> None. Credentials on the request are ignored.</p>\n","operationId":"getTokenSigningKeySet","responses":{"200":{"description":"The published JWK Set, as `application/jwk-set+json` (the default) or `application/json` (when requested); the body is identical. `Cache-Control: public, max-age=300`.","headers":{"Cache-Control":{"description":"Verifiers and shared caches may keep the set for 300 seconds.","style":"simple","schema":{"type":"string","example":"public, max-age=300"}}},"content":{"application/jwk-set+json":{"examples":{"Steady state: next, active and one previous key":{"summary":"After a rotation, while the former key's last tokens are still live","description":"Steady state: next, active and one previous key","value":{"keys":[{"kty":"RSA","use":"sig","alg":"RS256","kid":"IudAOqu99DPxaW1eBV9QIxjsvy4nTdqBEPECl7ZzzrA","e":"AQAB","n":"qZT113pyslTBp_u6Y6N2bUwSva6bfZIl5uDpDGclEws5qciEQfEQdHzSVnfPxs7jd4WWFeHeH6po_5TDKSbwd4zDKsKJAntic3MUZSG09fccCuczeISb9uAvDjxLCFLNkNTt-Tpr4ZSixTcXIIRTSwIEar2EvfT_K87Hz5cfeK4uDm-z_3FroVflUCffrHf6dcJWATGo3LinJax7SX1MKonwaoN67CHz3gF2OQz8LJ-YgoGKj58n-A9Kys734id2kdmZswNRpi7tADqNuMqe3lxQPxcdgyIYVfmPGeRbPHqJNOstJG8MA4JU4GNtrA4yD2T5ExBZ-ktwhc0_S9xd-a-ytB8KOFi-yrDyuux5tDw_NcrIeIPhbzcgAcrs2tYvZDUIMpBWgBboa4Cypi5TzPo2JPRqnKFxKyCS4kK6poURR5LpPl9ukjcLyVZLC-JSp4DU2C_kKk3G7KFtNCuZC2iMasCIK2nFFkwoq0kBj8RnaB2ArLqeO2XVqumkvqr7"},{"kty":"RSA","use":"sig","alg":"RS256","kid":"1nTDy0rLAusQv3K8kZWsaxJ9ZRrB6eC-Uv9TkQlP7Eo","e":"AQAB","n":"yI7ta1xME_mAXeYTYGyjE5fM3LZtN2YQFjLZHEAIqXQjRXw6b7Qk4JYmEb7rOSK37iOlLHwDYGNhgqGwaiLBGDJHetadj1jIHn77Rj2odh0jiHMF5ri-9qcNI4g8TxABdNu9UNbZ1QwBVjgF-AJ0FscS6C8RqxF4YqogyksQiu8_lI6rrHaAbdqhO9FjQm-6jAziqxyDWuQKz0GAE-0Knugq8isOkaACjU7rRZi9jzErtXtOSCJJbTGCljDXJNeYBUGXFQkcQsuIbQknwQai4iRmbqIC1QgrNtfWpw4mvgEyr4mckNtLklJDNTr2kvp_2hfFpQ_f_anDsPXCmKK4mdZQbBxnU2COrC20QxjbCznknw70BV__AQ5d5tT88i8MHgvzkTixpXbMoU1uYXMssejkezgytMoaJdVmqLdZh-0jthelfh3-BhNWJBiOfzGCVz-gg7hbvjlLBKlexBQw8XqQXX928nI75UU2c8JeNSK84cy7qbY_XHorqgTUMrdr"},{"kty":"RSA","use":"sig","alg":"RS256","kid":"jEDSzSpRkMOfW3hkBlIL6czVCyWzJhzjclcaPJtw7FA","e":"AQAB","n":"kh8sil9eAWm5Gk1NfxP0jso2jHynQUHLW4bD3Pnsil1pcL3OkOgVQzcTw6DdTxG3H71rs9ApHxKWy8D-Gwbz7q-rltBi2NoVNVvPXntE4RRpCFeKLUVH48CSNkW_d1BiKNoilEqrTJD7JsR1tVqKB1A_UMXZ6VQwkwzUGx4_4-LkER4RcHgd1jCVHAo1oCHffumE8FqFgaiSvy76ldjrNnHUHZQOvrMjXQ2FUG3d8il9XL2khDmfL3l8VaikPsY_6v1E8-tCGs0C72UxkFEaljOHTpjBgxBPvHeSVmePMdWVmZOd9zZWGa8Plm9dtIptOacUPkcUQX-jMGznnwdLymg6f3X9byV52YQziv3hZBWXRI6dAUQ3FoikV2v7x0WY_0Cr5HnDovT2lx75s7gI2w8j4yj7QyyczPM0kiQed77-JuI6TLERSy0LvC90Yf4TZ3wO_psC0tcv5tSBswvLK7bSBQ1FJWN9l8M7GPhooWrUNfFCdcNBmTPgAW-avMDN"}]}},"No rotation yet: next and active keys":{"summary":"A deployment whose first key has never been rotated","description":"No rotation yet: next and active keys","value":{"keys":[{"kty":"RSA","use":"sig","alg":"RS256","kid":"IudAOqu99DPxaW1eBV9QIxjsvy4nTdqBEPECl7ZzzrA","e":"AQAB","n":"qZT113pyslTBp_u6Y6N2bUwSva6bfZIl5uDpDGclEws5qciEQfEQdHzSVnfPxs7jd4WWFeHeH6po_5TDKSbwd4zDKsKJAntic3MUZSG09fccCuczeISb9uAvDjxLCFLNkNTt-Tpr4ZSixTcXIIRTSwIEar2EvfT_K87Hz5cfeK4uDm-z_3FroVflUCffrHf6dcJWATGo3LinJax7SX1MKonwaoN67CHz3gF2OQz8LJ-YgoGKj58n-A9Kys734id2kdmZswNRpi7tADqNuMqe3lxQPxcdgyIYVfmPGeRbPHqJNOstJG8MA4JU4GNtrA4yD2T5ExBZ-ktwhc0_S9xd-a-ytB8KOFi-yrDyuux5tDw_NcrIeIPhbzcgAcrs2tYvZDUIMpBWgBboa4Cypi5TzPo2JPRqnKFxKyCS4kK6poURR5LpPl9ukjcLyVZLC-JSp4DU2C_kKk3G7KFtNCuZC2iMasCIK2nFFkwoq0kBj8RnaB2ArLqeO2XVqumkvqr7"},{"kty":"RSA","use":"sig","alg":"RS256","kid":"1nTDy0rLAusQv3K8kZWsaxJ9ZRrB6eC-Uv9TkQlP7Eo","e":"AQAB","n":"yI7ta1xME_mAXeYTYGyjE5fM3LZtN2YQFjLZHEAIqXQjRXw6b7Qk4JYmEb7rOSK37iOlLHwDYGNhgqGwaiLBGDJHetadj1jIHn77Rj2odh0jiHMF5ri-9qcNI4g8TxABdNu9UNbZ1QwBVjgF-AJ0FscS6C8RqxF4YqogyksQiu8_lI6rrHaAbdqhO9FjQm-6jAziqxyDWuQKz0GAE-0Knugq8isOkaACjU7rRZi9jzErtXtOSCJJbTGCljDXJNeYBUGXFQkcQsuIbQknwQai4iRmbqIC1QgrNtfWpw4mvgEyr4mckNtLklJDNTr2kvp_2hfFpQ_f_anDsPXCmKK4mdZQbBxnU2COrC20QxjbCznknw70BV__AQ5d5tT88i8MHgvzkTixpXbMoU1uYXMssejkezgytMoaJdVmqLdZh-0jthelfh3-BhNWJBiOfzGCVz-gg7hbvjlLBKlexBQw8XqQXX928nI75UU2c8JeNSK84cy7qbY_XHorqgTUMrdr"}]}}}},"application/json":{"examples":{"Steady state: next, active and one previous key":{"summary":"After a rotation, while the former key's last tokens are still live","description":"Steady state: next, active and one previous key","value":{"keys":[{"kty":"RSA","use":"sig","alg":"RS256","kid":"IudAOqu99DPxaW1eBV9QIxjsvy4nTdqBEPECl7ZzzrA","e":"AQAB","n":"qZT113pyslTBp_u6Y6N2bUwSva6bfZIl5uDpDGclEws5qciEQfEQdHzSVnfPxs7jd4WWFeHeH6po_5TDKSbwd4zDKsKJAntic3MUZSG09fccCuczeISb9uAvDjxLCFLNkNTt-Tpr4ZSixTcXIIRTSwIEar2EvfT_K87Hz5cfeK4uDm-z_3FroVflUCffrHf6dcJWATGo3LinJax7SX1MKonwaoN67CHz3gF2OQz8LJ-YgoGKj58n-A9Kys734id2kdmZswNRpi7tADqNuMqe3lxQPxcdgyIYVfmPGeRbPHqJNOstJG8MA4JU4GNtrA4yD2T5ExBZ-ktwhc0_S9xd-a-ytB8KOFi-yrDyuux5tDw_NcrIeIPhbzcgAcrs2tYvZDUIMpBWgBboa4Cypi5TzPo2JPRqnKFxKyCS4kK6poURR5LpPl9ukjcLyVZLC-JSp4DU2C_kKk3G7KFtNCuZC2iMasCIK2nFFkwoq0kBj8RnaB2ArLqeO2XVqumkvqr7"},{"kty":"RSA","use":"sig","alg":"RS256","kid":"1nTDy0rLAusQv3K8kZWsaxJ9ZRrB6eC-Uv9TkQlP7Eo","e":"AQAB","n":"yI7ta1xME_mAXeYTYGyjE5fM3LZtN2YQFjLZHEAIqXQjRXw6b7Qk4JYmEb7rOSK37iOlLHwDYGNhgqGwaiLBGDJHetadj1jIHn77Rj2odh0jiHMF5ri-9qcNI4g8TxABdNu9UNbZ1QwBVjgF-AJ0FscS6C8RqxF4YqogyksQiu8_lI6rrHaAbdqhO9FjQm-6jAziqxyDWuQKz0GAE-0Knugq8isOkaACjU7rRZi9jzErtXtOSCJJbTGCljDXJNeYBUGXFQkcQsuIbQknwQai4iRmbqIC1QgrNtfWpw4mvgEyr4mckNtLklJDNTr2kvp_2hfFpQ_f_anDsPXCmKK4mdZQbBxnU2COrC20QxjbCznknw70BV__AQ5d5tT88i8MHgvzkTixpXbMoU1uYXMssejkezgytMoaJdVmqLdZh-0jthelfh3-BhNWJBiOfzGCVz-gg7hbvjlLBKlexBQw8XqQXX928nI75UU2c8JeNSK84cy7qbY_XHorqgTUMrdr"},{"kty":"RSA","use":"sig","alg":"RS256","kid":"jEDSzSpRkMOfW3hkBlIL6czVCyWzJhzjclcaPJtw7FA","e":"AQAB","n":"kh8sil9eAWm5Gk1NfxP0jso2jHynQUHLW4bD3Pnsil1pcL3OkOgVQzcTw6DdTxG3H71rs9ApHxKWy8D-Gwbz7q-rltBi2NoVNVvPXntE4RRpCFeKLUVH48CSNkW_d1BiKNoilEqrTJD7JsR1tVqKB1A_UMXZ6VQwkwzUGx4_4-LkER4RcHgd1jCVHAo1oCHffumE8FqFgaiSvy76ldjrNnHUHZQOvrMjXQ2FUG3d8il9XL2khDmfL3l8VaikPsY_6v1E8-tCGs0C72UxkFEaljOHTpjBgxBPvHeSVmePMdWVmZOd9zZWGa8Plm9dtIptOacUPkcUQX-jMGznnwdLymg6f3X9byV52YQziv3hZBWXRI6dAUQ3FoikV2v7x0WY_0Cr5HnDovT2lx75s7gI2w8j4yj7QyyczPM0kiQed77-JuI6TLERSy0LvC90Yf4TZ3wO_psC0tcv5tSBswvLK7bSBQ1FJWN9l8M7GPhooWrUNfFCdcNBmTPgAW-avMDN"}]}},"No rotation yet: next and active keys":{"summary":"A deployment whose first key has never been rotated","description":"No rotation yet: next and active keys","value":{"keys":[{"kty":"RSA","use":"sig","alg":"RS256","kid":"IudAOqu99DPxaW1eBV9QIxjsvy4nTdqBEPECl7ZzzrA","e":"AQAB","n":"qZT113pyslTBp_u6Y6N2bUwSva6bfZIl5uDpDGclEws5qciEQfEQdHzSVnfPxs7jd4WWFeHeH6po_5TDKSbwd4zDKsKJAntic3MUZSG09fccCuczeISb9uAvDjxLCFLNkNTt-Tpr4ZSixTcXIIRTSwIEar2EvfT_K87Hz5cfeK4uDm-z_3FroVflUCffrHf6dcJWATGo3LinJax7SX1MKonwaoN67CHz3gF2OQz8LJ-YgoGKj58n-A9Kys734id2kdmZswNRpi7tADqNuMqe3lxQPxcdgyIYVfmPGeRbPHqJNOstJG8MA4JU4GNtrA4yD2T5ExBZ-ktwhc0_S9xd-a-ytB8KOFi-yrDyuux5tDw_NcrIeIPhbzcgAcrs2tYvZDUIMpBWgBboa4Cypi5TzPo2JPRqnKFxKyCS4kK6poURR5LpPl9ukjcLyVZLC-JSp4DU2C_kKk3G7KFtNCuZC2iMasCIK2nFFkwoq0kBj8RnaB2ArLqeO2XVqumkvqr7"},{"kty":"RSA","use":"sig","alg":"RS256","kid":"1nTDy0rLAusQv3K8kZWsaxJ9ZRrB6eC-Uv9TkQlP7Eo","e":"AQAB","n":"yI7ta1xME_mAXeYTYGyjE5fM3LZtN2YQFjLZHEAIqXQjRXw6b7Qk4JYmEb7rOSK37iOlLHwDYGNhgqGwaiLBGDJHetadj1jIHn77Rj2odh0jiHMF5ri-9qcNI4g8TxABdNu9UNbZ1QwBVjgF-AJ0FscS6C8RqxF4YqogyksQiu8_lI6rrHaAbdqhO9FjQm-6jAziqxyDWuQKz0GAE-0Knugq8isOkaACjU7rRZi9jzErtXtOSCJJbTGCljDXJNeYBUGXFQkcQsuIbQknwQai4iRmbqIC1QgrNtfWpw4mvgEyr4mckNtLklJDNTr2kvp_2hfFpQ_f_anDsPXCmKK4mdZQbBxnU2COrC20QxjbCznknw70BV__AQ5d5tT88i8MHgvzkTixpXbMoU1uYXMssejkezgytMoaJdVmqLdZh-0jthelfh3-BhNWJBiOfzGCVz-gg7hbvjlLBKlexBQw8XqQXX928nI75UU2c8JeNSK84cy7qbY_XHorqgTUMrdr"}]}}}}}},"406":{"description":"The `Accept` header admits neither `application/jwk-set+json` nor `application/json`. Status line only: no body and no `Content-Type` (RFC 9110 §15.5.7).","content":{"application/jwk-set+json":{"schema":{"type":"string"}},"application/json":{"schema":{"type":"string"}}}},"503":{"description":"No signing key set is held: it could not be resolved from custody, was refused as unsound, or is past its stale bound. No key set is published — never an empty `keys` array — and the response is not cacheable. Retry after the `Retry-After` interval. Served as `application/problem+json` whatever the `Accept` header.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"30"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"Signing key set unavailable":{"description":"Signing key set unavailable","value":{"type":"https://docs.ankatech.co/errors/token-signing-unavailable","title":"Token Signing Unavailable","status":503,"detail":"The token signing key set is temporarily unavailable; no token can be issued or published.","instance":"/api/v3/auth/.well-known/jwks.json","timestamp":1790503200}}}}}}},"security":[]}},"/api/v3/auth/token/s/{tokenId}":{"delete":{"tags":["Token Management"],"summary":"Admin: Force revoke any token","description":"Forcibly revoke a token by JTI.\n\n**Authorization model:**\n- `platform.token.revoke`: revoke any tenant's token. Requires PRIVATE_CLOUD\n  or ON_PREMISE deployment — rejected in SaaS mode.\n- `auth.tenant.token.revoke`: revoke tokens belonging to users in the caller's\n  own tenant. Works in all deployment modes.\n- Both scopes: whatever either covers. The token's tenant is the one its issuance\n  record names, never a request parameter.\n\n**Callers:** a person's access token holding one of the two scopes, or an\nadministrative workload's token under `Authorization: DPoP` with a proof for this\nrequest (RFC 9449). A workload acts in the tenant its token names, and only a\nplatform-tenant workload may hold `platform.token.revoke`.\n\n**Anti-enumeration:** Returns 404 when the JTI is not found OR belongs to a\ntenant the caller does not reach (indistinguishable from non-existent). A caller holding\nonly `platform.token.revoke` where it reaches no tenant is refused (403) before the JTI is\nlooked up, whatever the JTI.\n","operationId":"forceRevokeToken","parameters":[{"name":"tokenId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Token revoked successfully"},"401":{"description":"No access token was presented, or the one presented is not accepted. A person's token is challenged with `WWW-Authenticate: Bearer`. An administrative workload's token is accepted only under `Authorization: DPoP` with a proof bound to the token and to this request (RFC 9449); otherwise the challenge is `WWW-Authenticate: DPoP error=\"invalid_token\"` (the token was presented under `Bearer`, or is not bound to a key) or `WWW-Authenticate: DPoP error=\"invalid_dpop_proof\"` (the proof is missing, malformed, already used, or names another method or URL). The body is the same in every case.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Token missing, rejected, or a workload token without a valid DPoP proof":{"description":"1. Token missing, rejected, or a workload token without a valid DPoP proof","value":{"type":"https://docs.ankatech.co/errors/unauthorized","title":"Unauthorized","status":401,"detail":"Authentication is required to call this endpoint.","instance":"/api/v3/auth/token/s/3f2b8c1e-9d4a-4e7b-a5c6-1b2d3e4f5a6b","timestamp":1765057500,"extensions":{"code":"UNAUTHENTICATED"}}}}}}},"403":{"description":"The caller holds neither revoke scope; or it holds only platform.token.revoke where that scope reaches no tenant (a SaaS or unknown deployment, or a caller outside the platform tenant), decided before the token is looked up. An administrative workload outside the platform tenant that holds a `platform.token.*` scope is refused, whatever the operation; a tenant workload acts with `auth.tenant.token.*` in the tenant its token names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Token not found, or out of the caller's reach: the same answer for both","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Two causes, told apart by `type`. **`revocation-store-unavailable`** (`Retry-After: 5`): the revocation store refused the blacklist write, and the token was NOT revoked and is still valid. **`/problems/dependency-not-ready` with `Retry-After`: the store that records each DPoP proof once could not decide whether this proof was already used, so the workload's request was neither admitted nor refused as a bad credential, and nothing changed.**","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"5"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The DPoP proof could not be checked for replay":{"description":"1. The DPoP proof could not be checked for replay","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/token/introspect"}}}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/tenant/users/{userId}/sessions/{sessionId}":{"delete":{"tags":["Tenant User Sessions"],"summary":"End one of a member's sessions in your tenant","description":"Ends one live session of a member whose current tenant is the tenant of the caller's access\ntoken. From the answer on, its access tokens are refused by every service of the platform, its\nrefresh token is refused with <code>invalid_grant</code>, and it leaves the list. The member's\nother sessions keep working.\n\nA session of the member that is currently in another tenant is answered <code>404</code>, like\nan unknown or ended one, and keeps working.\n\nThe revocation is recorded as <code>auth.session.revoked-by-administrator</code>, naming the\nadministrator as the actor and the member as the target, before the answer.\n","operationId":"revokeTenantUserSession","parameters":[{"name":"userId","in":"path","description":"The member's identity identifier: the user id the tenant's user list shows.","required":true,"schema":{"type":"string","format":"uuid"},"example":"7c9e6679-7425-40de-944b-e07fc1f90ae7"},{"name":"sessionId","in":"path","description":"The session to end, as the list shows it.","required":true,"schema":{"type":"string","format":"uuid"},"example":"9b8a7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"}],"responses":{"204":{"description":"The session was ended."},"400":{"description":"`userId` or `sessionId` is not a UUID.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Malformed session identifier":{"description":"1. Malformed session identifier","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Parameter 'sessionId' is not valid","instance":"/api/v3/auth/account/sessions/not-a-session","timestamp":1765057500}}}}}},"401":{"description":"No access token was presented, or the one presented is not accepted. A person's token is challenged with `WWW-Authenticate: Bearer`. An administrative workload's token is accepted only under `Authorization: DPoP` with a proof bound to the token and to this request (RFC 9449); otherwise the challenge is `WWW-Authenticate: DPoP error=\"invalid_token\"` (the token was presented under `Bearer`, or is not bound to a key) or `WWW-Authenticate: DPoP error=\"invalid_dpop_proof\"` (the proof is missing, malformed, already used, or names another method or URL). The body is the same in every case.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"The access token does not carry `auth.tenant.token.revoke`. An administrative workload outside the platform tenant that holds a `platform.token.*` scope is refused, whatever the operation; a tenant workload acts with `auth.tenant.token.*` in the tenant its token names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Missing scope":{"description":"1. Missing scope","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"You do not have permission to access this resource.","instance":"/api/v3/auth/tenant/users/7c9e6679-7425-40de-944b-e07fc1f90ae7/sessions","timestamp":1765057500}}}}}},"404":{"description":"The identity holds no active membership of the caller's tenant, or no live session of the member in that tenant has that identifier. Nothing changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. A session in another tenant, unknown or ended":{"description":"1. A session in another tenant, unknown or ended","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"No session with this identifier","instance":"/api/v3/auth/account/sessions/4f1d2c3b-9a8e-4d7c-b6a5-0e1f2a3b4c5d","timestamp":1765057500}},"2. Not a member of this tenant":{"description":"2. Not a member of this tenant","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"No member with this identifier in this tenant","instance":"/api/v3/auth/tenant/users/7c9e6679-7425-40de-944b-e07fc1f90ae7/sessions","timestamp":1765057500}}}}}},"502":{"description":"The account service that confirms the membership could not be reached, or answered outside its contract. Nothing changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready`: the session store could not be read or written, and nothing changed; or, for a revocation, its audit record could not be confirmed after the sessions were ended, which stands; or an administrative workload's DPoP proof could not be checked for replay, and nothing was read or changed. Retry after `Retry-After`.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"5"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Session store unavailable":{"description":"1. Session store unavailable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/sessions/revoke-others"}}}}}},"504":{"description":"The account service that confirms the membership did not answer within the deadline. Nothing changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/platform/identities/{identityId}/sessions/{sessionId}":{"delete":{"tags":["Platform Identity Sessions"],"summary":"End one of an identity's sessions","description":"Ends one live session of an identity, in whatever tenant it is. From the answer on, its access\ntokens are refused by every service of the platform, its refresh token is refused with\n<code>invalid_grant</code>, and it leaves the list. The identity's other sessions keep working.\nA session that does not exist, has already ended or belongs to another identity is answered\n<code>404</code>, and nothing changes.\n\nThe caller must be signed in to the platform tenant: any other caller is refused with\n<code>403</code> before anything is read. Not available on a SaaS deployment.\n\nThe revocation is recorded as <code>auth.session.revoked-by-administrator</code>, naming the\nadministrator as the actor and the identity as the target, before the answer.\n","operationId":"revokePlatformIdentitySession","parameters":[{"name":"identityId","in":"path","description":"The identity's identifier: the subject (`sub`) of the person's access tokens.","required":true,"schema":{"type":"string","format":"uuid"},"example":"7c9e6679-7425-40de-944b-e07fc1f90ae7"},{"name":"sessionId","in":"path","description":"The session to end, as the list shows it.","required":true,"schema":{"type":"string","format":"uuid"},"example":"4f1d2c3b-9a8e-4d7c-b6a5-0e1f2a3b4c5d"}],"responses":{"204":{"description":"The session was ended."},"400":{"description":"`identityId` or `sessionId` is not a UUID.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Malformed session identifier":{"description":"1. Malformed session identifier","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Parameter 'sessionId' is not valid","instance":"/api/v3/auth/account/sessions/not-a-session","timestamp":1765057500}}}}}},"401":{"description":"No access token was presented, or the one presented is not accepted. A person's token is challenged with `WWW-Authenticate: Bearer`. An administrative workload's token is accepted only under `Authorization: DPoP` with a proof bound to the token and to this request (RFC 9449); otherwise the challenge is `WWW-Authenticate: DPoP error=\"invalid_token\"` (the token was presented under `Bearer`, or is not bound to a key) or `WWW-Authenticate: DPoP error=\"invalid_dpop_proof\"` (the proof is missing, malformed, already used, or names another method or URL). The body is the same in every case.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"`forbidden`: the access token does not carry the scope, or was not issued for the platform tenant, in which case nothing is read; or the deployment is SaaS. An administrative workload outside the platform tenant that holds a `platform.token.*` scope is refused, whatever the operation; a tenant workload acts with `auth.tenant.token.*` in the tenant its token names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Not the platform tenant, or missing scope":{"description":"1. Not the platform tenant, or missing scope","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Forbidden","status":403,"detail":"You do not have permission to access this resource.","instance":"/api/v3/auth/tenant/users/7c9e6679-7425-40de-944b-e07fc1f90ae7/sessions","timestamp":1765057500}},"2. SaaS deployment":{"description":"2. SaaS deployment","value":{"type":"https://docs.ankatech.co/errors/forbidden","title":"Deployment Mode Restriction","status":403,"detail":"This operation is only available in PRIVATE_CLOUD or ON_PREMISE deployments.","instance":"/api/v3/auth/platform/identities/7c9e6679-7425-40de-944b-e07fc1f90ae7/sessions","timestamp":1765057500}}}}}},"404":{"description":"No live session of the identity has that identifier. Nothing changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Unknown, ended or another identity's session":{"description":"1. Unknown, ended or another identity's session","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"No session with this identifier","instance":"/api/v3/auth/account/sessions/4f1d2c3b-9a8e-4d7c-b6a5-0e1f2a3b4c5d","timestamp":1765057500}}}}}},"503":{"description":"`/problems/dependency-not-ready`: the session store could not be read or written, and nothing changed; or, for a revocation, its audit record could not be confirmed after the sessions were ended, which stands; or an administrative workload's DPoP proof could not be checked for replay, and nothing was read or changed. Retry after `Retry-After`.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"5"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Session store unavailable":{"description":"1. Session store unavailable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/sessions/revoke-others"}}}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/account/sessions/{sessionId}":{"delete":{"tags":["Account Sessions"],"summary":"End one of your sessions","description":"Ends one live session of the person the access token names, in whatever tenant it is. From the\nanswer on, the session's access tokens are refused by every service of the platform, its refresh\ntoken is refused with <code>invalid_grant</code>, and it leaves the list. The person's other\nsessions keep working. Ending the session the request comes from is allowed: it is a sign-out\nof that session.\n\nA session that does not exist, has already ended, or belongs to another person is answered\n<code>404</code> alike, and nothing changes.\n\nThe revocation is recorded as <code>auth.session.revoked</code> before the answer.\n","operationId":"revokeAccountSession","parameters":[{"name":"sessionId","in":"path","description":"The session to end, as the list shows it.","required":true,"schema":{"type":"string","format":"uuid"},"example":"4f1d2c3b-9a8e-4d7c-b6a5-0e1f2a3b4c5d"}],"responses":{"204":{"description":"The session was ended."},"400":{"description":"`sessionId` is not a UUID.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Malformed session identifier":{"description":"1. Malformed session identifier","value":{"type":"https://docs.ankatech.co/errors/invalid-input","title":"Invalid Input","status":400,"detail":"Parameter 'sessionId' is not valid","instance":"/api/v3/auth/account/sessions/not-a-session","timestamp":1765057500}}}}}},"401":{"description":"No access token was presented, or the one presented is not accepted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"`impersonation-operation-forbidden`: the access token is an impersonation token, and nothing changes. `forbidden`: the access token is not a person's session.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/sessions","timestamp":1765057500}}}}}},"404":{"description":"No live session of this person has that identifier. Nothing changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Unknown, ended or another person's session":{"description":"1. Unknown, ended or another person's session","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"No session with this identifier","instance":"/api/v3/auth/account/sessions/4f1d2c3b-9a8e-4d7c-b6a5-0e1f2a3b4c5d","timestamp":1765057500}}}}}},"503":{"description":"`/problems/dependency-not-ready`: the session store could not be read or written, and nothing changed; or, for a revocation, its audit record could not be confirmed after the sessions were ended, which stands; or an administrative workload's DPoP proof could not be checked for replay, and nothing was read or changed. Retry after `Retry-After`.","headers":{"Retry-After":{"description":"Seconds before a retry can succeed.","style":"simple","schema":{"type":"string","example":"5"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Session store unavailable":{"description":"1. Session store unavailable","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/sessions/revoke-others"}}}}}}},"security":[{"bearerAuth":[]}]}},"/api/v3/auth/account/password":{"delete":{"tags":["Account Security"],"summary":"Remove your password","description":"Removes the password of the person the access token names, who then signs in with passkeys or other\nfactors only. A password is a phishable secret; a person whose memberships all accept a passkey can\ngo passwordless.\n\n**Never a lock-out.** The removal is refused with <code>409\nauthenticator-removal-would-lock-out</code> when a membership the person can enter today is entered\nonly with the password. The refusal names no tenant, and nothing is removed: register a passkey\nfirst. A PLATFORM_SUPER_ADMIN's removal is refused with <code>409\nlast-platform-super-admin-would-lock-out</code> when no PLATFORM_SUPER_ADMIN could sign in after it.\n\nA person who holds no password is answered <code>404</code>.\n\nSensitive: <code>x-ankasecure-fresh-authentication: required</code>.\n","operationId":"deleteAccountPassword","responses":{"204":{"description":"The password is removed."},"401":{"description":"Two causes, told apart by the RFC 9457 `type`. No access token was presented, or the one presented is expired, revoked or otherwise not accepted. Or (`reauthentication-required`) the person's last authentication is older than the reauthentication window (`auth.reauthentication.max-age-seconds`, 300 seconds by default): the answer carries `WWW-Authenticate: Bearer error=\"insufficient_user_authentication\", error_description=\"...\", max_age=\"<seconds>\"` and the window in `extensions.maxAge`; nothing is read or changed, and the person reauthenticates (`max_age` on the `refresh_token` grant) and retries. An impersonation token is refused with `403` instead, and tokens of applications and actors are never refused for it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The last authentication is older than the window":{"description":"1. The last authentication is older than the window","value":{"type":"https://docs.ankatech.co/errors/reauthentication-required","title":"Reauthentication Required","status":401,"detail":"This operation requires a more recent authentication. Reauthenticate and retry the request; nothing was changed.","instance":"/api/v3/auth/account/recovery-codes","timestamp":1765057500,"extensions":{"maxAge":300}}}}}}},"403":{"description":"Two causes, told apart by the RFC 9457 `type`. `impersonation-operation-forbidden`: the access token is an impersonation token; the person it names is the tenant's own user, so the operation is refused before anything is read or changed. `forbidden`: the access token is not a person's session (it records no sign-in method).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. Acting on behalf of a tenant":{"description":"1. Acting on behalf of a tenant","value":{"type":"https://docs.ankatech.co/errors/impersonation-operation-forbidden","title":"Operation Not Available While Impersonating","status":403,"detail":"This operation is not available while acting on behalf of a tenant.","instance":"/api/v3/auth/account/authenticators","timestamp":1765057500}}}}}},"404":{"description":"The person holds no password.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. No password held":{"description":"1. No password held","value":{"type":"https://docs.ankatech.co/errors/not-found","title":"Resource Not Found","status":404,"detail":"The identity holds no password","instance":"/api/v3/auth/account/password","timestamp":1765057500}}}}}},"409":{"description":"`authenticator-removal-would-lock-out`: a membership is entered only with the password. `last-platform-super-admin-would-lock-out`: no PLATFORM_SUPER_ADMIN could sign in after the removal. Nothing is removed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. A tenant's policy is satisfied only through the password":{"description":"1. A tenant's policy is satisfied only through the password","value":{"type":"https://docs.ankatech.co/errors/authenticator-removal-would-lock-out","title":"Authenticator Removal Would Lock Out","status":409,"detail":"Removing this authenticator would leave no way to sign in to one of your memberships. Add another authenticator first.","instance":"/api/v3/auth/account/password","timestamp":1765057500}},"2. The password is the only super administrator's last way in":{"description":"2. The password is the only super administrator's last way in","value":{"type":"https://docs.ankatech.co/errors/last-platform-super-admin-would-lock-out","title":"Last Platform Super Administrator","status":409,"detail":"This would leave the platform without a PLATFORM_SUPER_ADMIN who can sign in. Give another active platform member PLATFORM_SUPER_ADMIN and a way to sign in first; nothing was changed.","instance":"/api/v3/auth/account/password","timestamp":1765057500}}}}}},"502":{"description":"The account service could not be reached, or answered outside the contract of this operation. Nothing is reported as changed; repeat the request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"504":{"description":"The account service did not answer within this operation's deadline. Repeat the request; a change that completed meanwhile is visible in the list.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"`/problems/dependency-not-ready`: the account service does not serve this operation yet (a platform being upgraded runs the previous release of it for a while). Nothing changed; repeat the request after `Retry-After`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"},"examples":{"1. The account service runs the previous release":{"description":"1. The account service runs the previous release","value":{"type":"/problems/dependency-not-ready","title":"Service Dependency Not Ready","status":503,"detail":"A required dependency is temporarily unavailable. Retry after the indicated interval.","instance":"/api/v3/auth/account/authenticators"}}}}}}},"security":[{"bearerAuth":[]}],"x-ankasecure-fresh-authentication":"required"}}},"components":{"schemas":{"TokenRequest":{"type":"object","description":"OAuth 2.0 token request supporting the authorization_code, client_credentials, refresh_token\nand token-exchange grant types. Required fields depend on grant_type value.\n","example":{"grant_type":"authorization_code","code":"uM3rQ9xT2kW7vB5nY1cL8hJ4gF6dS0aZ_pE3oN7iR2k","code_verifier":"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"},"properties":{"grant_type":{"type":"string","description":"OAuth 2.0 grant type per RFC 6749. Determines which authentication flow to use.\n\nSupported values:\n- authorization_code: Redeems the authorization code a sign-in at the authorization challenge\n  endpoint ended in, with its PKCE code_verifier (RFC 6749 Section 4.1.3, RFC 7636)\n- client_credentials: Application authentication with client_id/client_secret (RFC 6749 Section 4.4)\n- refresh_token: Token refresh using refresh token, optionally switching tenant (RFC 6749 Section 6)\n- urn:ietf:params:oauth:grant-type:token-exchange: Token exchange for impersonation (RFC 8693)\n","enum":["authorization_code","client_credentials","refresh_token","urn:ietf:params:oauth:grant-type:token-exchange"],"example":"authorization_code","minLength":1},"tenant_id":{"type":"string","format":"uuid","description":"Tenant UUID.\n\n- refresh_token: OPTIONAL. Another ACTIVE membership of the same identity to switch to;\n  absent or equal to the session's tenant, the session refreshes its own tenant.\n","example":"00000000-0000-0000-0000-000000000003","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"client_id":{"type":"string","description":"grant_type=client_credentials: REQUIRED. The UUID of the cryptographic ACTOR or\nadministrative workload that holds the credential and is authenticating. An APPLICATION groups\nactors, holds no credential and cannot authenticate, so its id is never presented here. A\nperson's grants (authorization_code, refresh_token) send none.\n","example":"550e8400-e29b-41d4-a716-446655440000"},"client_secret":{"type":"string","format":"password","description":"Application secret. REQUIRED when grant_type=client_credentials.\n\nThe secret credential for the application, validated against the stored hash.\nMinimum 8 characters. This secret is generated when the application is created\nand should be stored securely (environment variables, secret manager).\n","example":"c8d9e7f6a5b4c3d2e1f0a9b8c7d6e5f4","maxLength":2147483647,"minLength":8},"client_assertion_type":{"type":"string","description":"Which assertion profile the client_assertion follows. REQUIRED when\nclient_assertion is present. Two values are admitted:\n\n- urn:ietf:params:oauth:client-assertion-type:jwt-bearer (RFC 7523 §2.2) — the\n  actor signed the assertion itself, with a key registered for it here.\n- urn:ankatech:params:oauth:client-assertion-type:federated-jwt — an\n  external issuer this deployment trusts minted the token for the workload, and\n  an (issuer, subject) declaration binds it to the actor named by client_id.\n\nThis declared value is the ONLY thing that selects between them. The token's own\ncontents are never inspected to make that choice.\n\nAn unrecognised value is a 400 invalid_request, not a credential failure: the\nclient asked for a profile this server does not implement, which is a malformed\nrequest rather than a wrong credential.\n","example":"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"},"client_assertion":{"type":"string","description":"The assertion, in JWS compact serialization. REQUIRED when\ngrant_type=client_credentials and the actor authenticates with an assertion rather\nthan a shared secret. Bounded at 16 KiB; a larger value is refused as a credential\nfailure, indistinguishably from any other.\n\nUnder jwt-bearer the actor signed it: iss and sub are both the actor UUID, aud is\nthe audience this deployment returned when the key was registered, and jti is\nsingle-use.\n\nUnder federated-jwt an external issuer signed it: iss is that issuer, sub is the\nworkload subject bound to the actor, aud is the audience this deployment derived\nfor the issuer, and jti is optional.\n","example":"eyJhbGciOiJSUzI1NiIsImtpZCI6IndsLTIwMjYtMDEifQ.eyJpc3MiOiI...ln0.Nn8xW-signature"},"exchange_context":{"type":"string","format":"uuid","description":"OPTIONAL Exchange Context UUID for grant_type=client_credentials when the\nclient_id is a Cryptographic Actor. The issued JWT carries the supplied\nvalue in the exchangeContextId claim, binding the token to operations\nconducted under that Exchange Context.\n\nOmit this parameter for Actors that operate without a specific Exchange\nContext (internal operations) — the emitted JWT then carries null\nexchangeContextId and the cryptographic service will match grants that also\nhave null exchange_context_id.\n","example":"44444444-4444-4444-4444-444444444444"},"refresh_token":{"type":"string","description":"Opaque refresh token for grant_type=refresh_token.\n\nA person's session presents it in the httpOnly refresh cookie, or here, without a client_id.\nIf both are present, this parameter takes precedence.\n","example":"q7Lw2Zr9Tx4Nc8Vb1Mk6Pw"},"subject_token":{"type":"string","description":"JWT access token to exchange. REQUIRED when grant_type=urn:ietf:params:oauth:grant-type:token-exchange.\n\nThis is the platform admin's JWT token that will be exchanged for a tenant-scoped token.\nThe token must have platform-level privileges to perform impersonation.\nFormat: JWT with RS256 signature.\n","example":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."},"subject_token_type":{"type":"string","format":"uri","description":"Type of subject_token per RFC 8693. OPTIONAL for token exchange.\n\nStandard value: urn:ietf:params:oauth:token-type:access_token\nIf omitted, defaults to access_token type.\n","enum":["urn:ietf:params:oauth:token-type:access_token"],"example":"urn:ietf:params:oauth:token-type:access_token"},"requested_token_type":{"type":"string","format":"uri","description":"Type of requested token per RFC 8693. OPTIONAL for token exchange.\n\nStandard value: urn:ietf:params:oauth:token-type:access_token\nIf omitted, defaults to access_token type.\n","enum":["urn:ietf:params:oauth:token-type:access_token"],"example":"urn:ietf:params:oauth:token-type:access_token"},"resource":{"type":"string","format":"uri","description":"Target tenant URI for impersonation. REQUIRED when grant_type=urn:ietf:params:oauth:grant-type:token-exchange.\n\nFormat: urn:ankatech:tenant:<tenant-uuid>\nExample: urn:ankatech:tenant:00000000-0000-0000-0000-000000000003\n\nThis identifies which tenant the impersonation token should target.\nThe platform admin will receive a token with tenant-scoped privileges.\n","example":"urn:ankatech:tenant:00000000-0000-0000-0000-000000000003","pattern":"^urn:ankatech:tenant:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"scope":{"type":"string","description":"Space-delimited list of requested scopes. OPTIONAL for all grant types.\n\nAccepted and ignored: the issued token carries the scopes its subject's role\nassignments grant, whatever this parameter requests. A value here neither narrows\nnor widens them, and is never an error.\n","example":"admin.tenant.user.read admin.tenant.user.update"},"code":{"type":"string","description":"The authorization code of a completed sign-in at the authorization challenge endpoint. REQUIRED\nwhen grant_type=authorization_code.\n\nValid for 60 seconds and consumed by its first presentation, whatever the outcome: a second\npresentation, a wrong code_verifier or an expired code is invalid_grant.\n","example":"uM3rQ9xT2kW7vB5nY1cL8hJ4gF6dS0aZ_pE3oN7iR2k","maxLength":43,"minLength":43},"code_verifier":{"type":"string","description":"The PKCE code_verifier whose S256 challenge started the sign-in (RFC 7636 Section 4.5). REQUIRED\nwhen grant_type=authorization_code. 43 to 128 characters of [A-Z a-z 0-9 - . _ ~].\n","example":"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk","maxLength":128,"minLength":43},"max_age":{"type":"string","description":"The oldest proof of identity, in seconds, the refreshed session may rest on (OIDC Core 1.0 max_age).\nOPTIONAL, and accepted only when grant_type=refresh_token without a switch to another tenant. A\nnon-negative integer. When the session's most recent authentication for its tenant is older, the\nrefresh is answered 403 insufficient_authorization with an auth_session that continues a fresh\nreauthentication at the authorization challenge endpoint; nothing is rotated.\n","example":"300","pattern":"^[0-9]{1,10}$"},"remember_me":{"type":"string","default":"false","description":"Remember this sign-in. OPTIONAL when grant_type=authorization_code.\n\nValues:\n- true: the refresh cookie is persistent, with a Max-Age equal to the seconds left to the\n  session's absolute expiry, and the longer Remember Me inactivity window applies\n  (auth.session.remember-me.inactivity-timeout-minutes). Ignored while\n  auth.session.remember-me.enabled is false.\n- false or omitted: the refresh cookie is a session cookie and the standard inactivity window\n  applies (auth.session.inactivity-timeout-minutes).\n\nRemember Me never extends the session's absolute expiry\n(auth.session.absolute-lifetime-minutes). Only applies to the authorization_code grant;\nrefreshes keep the session's choice.\n","example":true}},"required":["grant_type"]},"TokenResponse":{"type":"object","description":"OAuth 2.0 token response (RFC 6749 §5.1) for every grant. The token-exchange grant answers with the same format plus issued_token_type and scope (RFC 8693 §2.2.1), and without refresh_token and passwordChangeEligible","example":{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3YzllNjY3OS03NDI1LTQwZGUtOTQ0Yi1lMDdmYzFmOTBhZTciLCJ1c2VyVHlwZSI6IkhVTUFOIiwidGVuYW50SWQiOiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDMiLCJzY29wZXMiOlsiYWRtaW4udGVuYW50LnVzZXIuY3JlYXRlIl0sImlzcyI6Imh0dHBzOi8vZGV2LmFua2F0ZWNoLmNvL2F1dGgiLCJhdWQiOiJhbmthc2VjdXJlLWFkbWluIiwiZXhwIjoxNzM3NjY2MDAwLCJpYXQiOjE3Mzc2MzAwMDB9...","refresh_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3YzllNjY3OS03NDI1LTQwZGUtOTQ0Yi1lMDdmYzFmOTBhZTciLCJ0eXBlIjoicmVmcmVzaCIsInVzZXJUeXBlIjoiSFVNQU4iLCJ0ZW5hbnRJZCI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMyIsImlzcyI6Imh0dHBzOi8vZGV2LmFua2F0ZWNoLmNvL2F1dGgiLCJhdWQiOiJhbmthc2VjdXJlLWFkbWluIiwiZXhwIjoxNzM3NzAyMDAwLCJpYXQiOjE3Mzc2MzAwMDB9...","token_type":"Bearer","expires_in":36000,"passwordChangeEligible":true},"properties":{"access_token":{"type":"string","description":"JWT access token used for authenticating API requests","example":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."},"refresh_token":{"type":"string","description":"Sent as null on the grants that issue a person's session (authorization_code and refresh_token), which deliver the refresh token in an HttpOnly cookie, and on the client_credentials grant, which issues none (RFC 6749 §4.4.3). Absent on the token-exchange grant (RFC 8693 §2.2.1).","example":"q7Lw2Zr9Tx4Nc8Vb1Mk6Pw"},"token_type":{"type":"string","description":"Token type (RFC 6749 §7.1): 'Bearer' on every grant, except 'DPoP' (RFC 9449 §5) for an administrative workload's client_credentials grant sent with a DPoP proof, whose token is bound to the proof key","enum":["Bearer","DPoP"],"example":"Bearer"},"expires_in":{"type":"integer","format":"int32","description":"Access token lifetime in seconds (typically 36000 = 10 hours)","example":36000},"passwordChangeEligible":{"type":"boolean","description":"Whether the account this token names holds a password managed by ANKASecure and may therefore change it through POST /api/v3/auth/account/password/change. False for an account whose credential is held by an external identity provider, and false on every grant that does not resolve the fact (refresh_token, client_credentials). Present on the password, tenant-selection, client_credentials and refresh_token grants; absent on the token-exchange grant. It is a rendering hint for the console, never an authorization decision: the change endpoint refuses the same subject regardless of what this says.","example":true},"issued_token_type":{"type":"string","description":"URI identifying the type of the issued token (RFC 8693 §2.2.1). Present on the token-exchange grant only.","example":"urn:ietf:params:oauth:token-type:access_token"},"scope":{"type":"string","description":"Space-delimited list of the granted scopes (RFC 6749 §5.1, RFC 8693 §2.2.1). Present on the token-exchange grant only.","example":"admin.tenant.user.read admin.tenant.user.create admin.tenant.analytics.read"}},"required":["access_token","expires_in","token_type"]},"OAuth2ErrorResponse":{"type":"object","description":"OAuth 2.0 error response per RFC 6749 Section 5.2 with additional RFC 7807 fields","example":{"error":"invalid_grant","error_description":"The provided username or password is incorrect","error_uri":"https://docs.ankatech.co/errors/invalid_grant","type":"https://docs.ankatech.co/errors/invalid_grant","title":"Authentication Failed","status":401,"detail":"The provided username or password is incorrect","instance":"/api/v3/auth/token","timestamp":1737630000},"properties":{"error":{"type":"string","description":"OAuth 2.0 error code per RFC 6749 Section 5.2. Standard values:\n- invalid_request: Missing or malformed parameter\n- invalid_client: Client authentication failed\n- invalid_grant: Invalid credentials or expired refresh token\n- unsupported_grant_type: Grant type not recognized\n- invalid_scope: Requested scope invalid or exceeds granted\n- insufficient_authorization: A tenant switch needs further authentication (HTTP 403); the\n  answer carries an auth_session to continue with at the authorization challenge endpoint\n","enum":["invalid_request","invalid_client","invalid_grant","unsupported_grant_type","invalid_scope","insufficient_authorization"],"example":"invalid_grant"},"error_description":{"type":"string","description":"Human-readable ASCII text providing additional information about the error","example":"The provided username or password is incorrect"},"error_uri":{"type":"string","description":"URI identifying a human-readable web page with information about the error","example":"https://docs.ankatech.co/errors/invalid_grant"},"auth_session":{"type":"string","description":"Present only with `insufficient_authorization`: the step-up a tenant switch requires\n(draft-ietf-oauth-first-party-apps, section 5.2.2.1). It is bound to the session that presented the\nrefresh token and to the requested tenant. The client continues at\n`POST /api/v3/auth/authorize-challenge` with this `auth_session`, a new S256 `code_challenge`\nand no `action`, answers the step-up there, and redeems the authorization code it ends in on the\n`authorization_code` grant with the session's refresh token: the same session then continues in\nthe requested tenant. Single use, valid for at most 10 minutes; nothing was issued or rotated, and\nthe presented refresh token stays usable for its own tenant.\n","example":"jT4Wq7Zp2Lk9Rv1Xc6Bn3Md8Fs5Gh0Yu2Ei7Oa4Tw9"},"type":{"type":"string","description":"URI reference identifying the problem type (RFC 7807)","example":"https://docs.ankatech.co/errors/invalid_grant"},"title":{"type":"string","description":"Short, human-readable summary of the problem type (RFC 7807)","example":"Authentication Failed"},"status":{"type":"integer","format":"int32","description":"HTTP status code (RFC 7807)","example":401},"detail":{"type":"string","description":"Human-readable explanation specific to this occurrence (RFC 7807)","example":"The provided username or password is incorrect"},"instance":{"type":"string","description":"URI reference identifying the specific occurrence of the problem (RFC 7807)","example":"/api/v3/auth/token"},"timestamp":{"type":"integer","format":"int64","description":"Unix timestamp when the error occurred (RFC 7807 extension)","example":1737630000}},"required":["error"]},"ProblemDetail":{"type":"object","properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer","format":"int32"},"detail":{"type":"string"},"instance":{"type":"string","format":"uri"},"properties":{"type":"object","additionalProperties":{}}}},"ProblemDetails":{"type":"object","description":"Standard RFC 7807 problem envelope.","properties":{"type":{"type":"string","description":"URI reference to the problem type.","example":"https://docs.ankatech.co/errors/invalid-input"},"title":{"type":"string","description":"Short, human‑readable summary.","example":"Invalid input"},"status":{"type":"integer","format":"int32","description":"HTTP status code.","example":400},"detail":{"type":"string","description":"Human‑readable explanation of the error.","example":"Malformed request body: unable to parse JSON"},"instance":{"type":"string","description":"URI reference to the specific occurrence.","example":"/api/v3/auth/account/password/forgot"},"timestamp":{"type":"integer","format":"int64","description":"Epoch‑second timestamp of the error.","example":1745119200},"extensions":{"type":"object","additionalProperties":{},"description":"Optional extension bag."}}},"RevokeRequest":{"type":"object","description":"OAuth 2.0 token revocation request (RFC 7009). Submitted as application/x-www-form-urlencoded per RFC 7009 section 2.1 - this schema documents the FORM FIELDS, not a JSON body.","example":"token=Zm9vYmFyYmF6cXV4MTIzNDU2&token_type_hint=refresh_token","properties":{"token":{"type":"string","description":"Token to revoke - a JWT access token or an opaque refresh token. A refresh token is NOT a JWT: it is an opaque Base64URL value, and revoking one is the RFC 7009 section 2 mandatory half.","example":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...","minLength":1},"token_type_hint":{"type":"string","description":"Optional hint about token type (access_token or refresh_token)","enum":["access_token","refresh_token"],"example":"access_token"}},"required":["token"]},"IntrospectRequest":{"type":"object","description":"OAuth 2.0 token introspection request (RFC 7662). Submitted as application/x-www-form-urlencoded per RFC 7662 section 2.1 - this schema documents the FORM FIELDS, not a JSON body.","example":"token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...&token_type_hint=access_token","properties":{"token":{"type":"string","description":"Token to introspect - a JWT access token or an opaque refresh token","example":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...","minLength":1},"token_type_hint":{"type":"string","description":"Optional hint about token type (access_token or refresh_token)","enum":["access_token","refresh_token"],"example":"access_token"}},"required":["token"]},"IntrospectResponse":{"type":"object","description":"OAuth 2.0 token introspection response (RFC 7662)","example":{"active":true,"scopes":["admin.tenant.user.create","admin.tenant.user.read"],"username":"admin@acmecorp.com","token_type":"access_token","exp":1737666000,"iat":1737630000,"sub":"7c9e6679-7425-40de-944b-e07fc1f90ae7","aud":"ankasecure-admin","iss":"https://auth.example.com"},"properties":{"active":{"type":"boolean","description":"Whether the token is currently active (not expired, not revoked)","example":true},"scopes":{"type":"array","description":"Scopes carried by an active access token. OMITTED for a refresh token - a refresh token grants no scopes of its own, and reporting the scopes of the access token it could mint would describe a token that does not exist yet","example":["admin.tenant.user.create","admin.tenant.user.read"],"items":{"type":"string"}},"username":{"type":"string","description":"Username (email) for user tokens. OMITTED (not null) for application tokens - the response omits members that have no value","example":"admin@acmecorp.com"},"exp":{"type":"integer","format":"int64","description":"Expiration time (Unix timestamp)","example":1737666000},"iat":{"type":"integer","format":"int64","description":"Issued at time (Unix timestamp)","example":1737630000},"sub":{"type":"string","description":"Subject (user or application UUID)","example":"7c9e6679-7425-40de-944b-e07fc1f90ae7"},"aud":{"type":"string","description":"Audience (target service)","example":"ankasecure-admin"},"iss":{"type":"string","description":"Issuer (authentication service URL)","example":"https://auth.example.com"},"client_id":{"type":"string","description":"Client ID for application tokens. OMITTED (not null) for user tokens - the response omits members that have no value","example":"550e8400-e29b-41d4-a716-446655440000"},"token_type":{"type":"string","description":"Type of token (access_token or refresh_token)","example":"access_token"}},"required":["active"]},"AuthorizationChallengeRequest":{"type":"object","description":"A request to the authorization challenge endpoint (application/x-www-form-urlencoded). The first request of a sign-in carries only code_challenge and code_challenge_method=S256. Every later request carries the auth_session of the previous answer, the action it takes and that action's members. The first request of a tenant switch's step-up carries the auth_session the token endpoint answered, code_challenge and code_challenge_method=S256, and no action.","properties":{"action":{"type":"string","description":"The step this request takes. Absent on the first request.\n\n- passkey-assertion: a WebAuthn assertion made with the request options of the previous answer (credential_id, client_data_json, authenticator_data, signature, user_handle)\n- password: the sign-in address and the password (username, password)\n- ldap-password: a directory password verified by the tenant's LDAP directory (tenant_id, username, password)\n- totp: the code of the authenticator app, as the second factor after a password (otp)\n- recovery-code: the sign-in address and one recovery code, which opens recovery only (username, recovery_code)\n- select-tenant: the tenant chosen among the tenants of a tenant-selection answer (tenant_id)\n- passkey-registration-options: asks for the WebAuthn creation options of a new passkey (no member)\n- passkey-registration: the WebAuthn registration made with those options (credential_id, client_data_json, attestation_object, transports, cred_props_rk, display_name)\n- totp-enrolment: starts the enrolment of an authenticator app; the answer carries its secret once (no member)\n- totp-enrolment-verification: the first code of that app, which completes the enrolment (otp)\n- password-set: the password the person chooses (password)\n- federated: signs in through the identity provider of an offered federated path, in the browser (idp_id); the answer is redirect_to_web","enum":["passkey-assertion","password","ldap-password","totp","recovery-code","select-tenant","passkey-registration-options","passkey-registration","totp-enrolment","totp-enrolment-verification","password-set","federated"],"example":"passkey-assertion"},"auth_session":{"type":"string","description":"The auth_session of the previous answer. Every answer hands out a new one and the previous one is no longer accepted; a sign-in lasts at most 10 minutes from its first request. Absent on the first request of a sign-in; on the first request of a tenant switch's step-up, the auth_session the token endpoint answered with insufficient_authorization.","example":"cX7Jb2kYw9TqL4mVn8RzP1sD6fGhA3eK0uWoZ5iBtCy","maxLength":43,"minLength":43},"code_challenge":{"type":"string","description":"The PKCE challenge: BASE64URL(SHA-256(code_verifier)) without padding (RFC 7636). Required on the first request, refused on any other: the first request of a sign-in, or the first request of a tenant switch's step-up, which carries it with the switch's auth_session. The code_verifier is presented only at the token endpoint, with the authorization code.","example":"E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM","maxLength":43,"minLength":43},"code_challenge_method":{"type":"string","description":"The PKCE method. Only S256 is accepted; plain or an absent method is invalid_request.","enum":["S256"],"example":"S256"},"credential_id":{"type":"string","description":"passkey-assertion: the id of the credential that signed, base64url (the `id` of the WebAuthn AuthenticationResponseJSON). passkey-registration: the id of the new credential (the `id` of the RegistrationResponseJSON).","example":"AV3kUf7zq1yJm0pXr9tB2cQe","maxLength":1366},"client_data_json":{"type":"string","description":"passkey-assertion and passkey-registration: `response.clientDataJSON`, base64url.","example":"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoicTRabDh3MFhRbTF2WXAya1I3dE4zc0JmSGNKNnVEOWVBNWdMeFZvV2lFMCIsIm9yaWdpbiI6Imh0dHBzOi8vZGV2LmFua2F0ZWNoLmNvIn0","maxLength":8192},"authenticator_data":{"type":"string","description":"passkey-assertion: `response.authenticatorData`, base64url.","example":"dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvAdAAAABw","maxLength":8192},"signature":{"type":"string","description":"passkey-assertion: `response.signature`, base64url.","example":"MEUCIQDkKz3r9ZlB2pXyQ0mVt8sN4cJ7aF1eH6gD5uW2oL9iRgIgT3vY8bK1nM0qP7xZ4cS6dA2fE9hJ5lU8wR3tG1yB0oI","maxLength":2048},"user_handle":{"type":"string","description":"passkey-assertion: `response.userHandle`, base64url, when the authenticator returned one.","example":"Xw3k9VhQ0mT2Lr5pYc8aN1sJ7dFb4GzKe6uHxWqRo0y","maxLength":86},"tenant_id":{"type":"string","format":"uuid","description":"select-tenant: one tenant_id of the tenants the previous answer offered. ldap-password: the tenant whose LDAP directory verifies the password.","example":"00000000-0000-0000-0000-000000000003"},"username":{"type":"string","description":"password and recovery-code: the sign-in address. ldap-password: the directory username. The first request of an instance claim (claim_code): the sign-in address of the platform administrator the claim creates.","example":"jane.doe@acme.example","maxLength":320},"password":{"type":"string","format":"password","description":"password: the password. ldap-password: the directory password. password-set: the password the person chooses; the password policy decides it (15 to 128 characters, no composition rule, no common or compromised value, not containing the local part of the e-mail address). Never echoed, logged or stored by this service.","example":"harbour lantern quietly folding"},"otp":{"type":"string","description":"totp: the 6- to 8-digit code the authenticator app shows. A code is accepted once. totp-enrolment-verification: the first code of the app being enrolled.","example":"492039","maxLength":8,"minLength":6,"pattern":"^[0-9]{6,8}$"},"recovery_code":{"type":"string","description":"recovery-code: one of the identity's recovery codes, in any case, with or without its hyphens. It never yields a code or a token: it opens recovery, where only the enrolment of a new authenticator is admitted.","example":"7K3M-Q9TD-2XWH-B8NR-5FCJ-ZP4V","maxLength":64},"activation_token":{"type":"string","description":"First request only, with the PKCE challenge: the token of an activation link. The sign-in then starts in enrolment-required for the person the link names; the membership turns active once an authenticator its policy accepts is enrolled. At most one start parameter.","example":"eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI3YzFlNGIyYSJ9.c2lnbmF0dXJl","maxLength":3673},"invitation_token":{"type":"string","description":"First request only, with the PKCE challenge: the token of an invitation to a further tenant. The person signs in with the authenticators they hold; the invitation is accepted when they satisfy the inviting tenant's policy, or after an additional factor is enrolled. At most one start parameter.","example":"q3Zt0W8xYc1Rk5LmN7pQ2A","maxLength":22,"minLength":22},"enrolment_token":{"type":"string","description":"First request only, with the PKCE challenge: the single-use enrolment token an assisted recovery handed over. The sign-in starts in enrolment-required and yields a code only after a new authenticator is enrolled. At most one start parameter.","example":"q7Vw2ZbN0xR4tY8uI1oP5aS9dF3gH6jK2lZ7xC0vB4n","maxLength":43,"minLength":43},"attestation_object":{"type":"string","description":"passkey-registration: `response.attestationObject`, base64url.","example":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBdAAAAAA","maxLength":65536},"transports":{"type":"array","description":"passkey-registration: the transport hints of `response.getTransports()`, one parameter per hint, at most 8.","items":{"type":"string","maxLength":32,"pattern":"^[a-z-]+$"}},"cred_props_rk":{"type":"string","description":"passkey-registration: the `credProps.rk` client extension result, when the client reported it.","enum":["true","false"],"example":"true"},"display_name":{"type":"string","description":"passkey-registration: the name the person gives the passkey.","example":"Work laptop","maxLength":100},"idp_id":{"type":"string","format":"uuid","description":"federated: the idp_id of one federated path the previous answer offered. A provider the answer did not offer is the refusal.","example":"3f1c2a9e-4b7d-4e2a-9c11-0a2b3c4d5e6f"},"purpose":{"type":"string","description":"First request only, with the PKCE challenge: break_glass starts the emergency-access entry of a designated break-glass member. It carries no token and names no account: the answer asks for a passkey assertion, the same for every caller, and only a security key or a device-bound passkey of a designated member admits. At most one start parameter.","enum":["break_glass"],"example":"break_glass"},"claim_code":{"type":"string","description":"First request only, with the PKCE challenge and username: the single-use claim code the operator of the environment host minted for a new deployment that no administrator holds yet (43 base64url characters, valid one hour). The sign-in then starts in enrolment-required for the platform administrator it creates, named by username, who sets a password and enrols an authenticator app or a passkey. A code that is absent, wrong, expired or already used is refused and creates nothing; once the deployment is claimed every claim is refused with 409. Never echoed, logged or stored by this service. At most one start parameter.","example":"Jt5cX2mQ8vR1nY7bK4wL9pZ0sD3fH6gA2eU5iO8rT1y","maxLength":43,"minLength":43}}},"AuthorizationChallengePath":{"type":"object","description":"An alternative that would admit","properties":{"type":{"type":"string","description":"The kind of the path. passkey-enrolment is taken with passkey-registration-options and passkey-registration; totp-enrolment with totp-enrolment and totp-enrolment-verification, after a password the person proved or chose; federated with the action federated and this path's idp_id, in the browser.","enum":["passkey-assertion","totp","passkey-enrolment","totp-enrolment","federated"],"example":"passkey-enrolment"},"required_properties":{"type":"array","description":"The capability properties the authenticator must hold","items":{"type":"string"}},"missing_properties":{"type":"array","description":"The required properties the passkey just used lacks","items":{"type":"string"}},"idp_id":{"type":["string","null"],"format":"uuid","description":"type=federated only: the identity provider to sign in through, presented as idp_id with the action federated","example":"3f1c2a9e-4b7d-4e2a-9c11-0a2b3c4d5e6f"},"display_name":{"type":["string","null"],"description":"type=federated only: the name the person recognises the identity provider by","example":"Acme Logistics single sign-on"}},"required":["required_properties","type"]},"AuthorizationChallengeResponse":{"type":"object","description":"An answer of the authorization challenge endpoint: the authorization code (200), the next step of the sign-in (403 insufficient_authorization), or the move of the sign-in to the browser for a federated path (403 redirect_to_web). Members that do not belong to the answer's kind are absent.","properties":{"authorization_code":{"type":["string","null"],"description":"200 only. The authorization code: redeem it once, within 60 seconds, at POST /api/v3/auth/token with grant_type=authorization_code and the code_verifier.","example":"uM3rQ9xT2kW7vB5nY1cL8hJ4gF6dS0aZ_pE3oN7iR2k"},"error":{"type":["string","null"],"description":"403 only. insufficient_authorization: the sign-in continues with another step. redirect_to_web: the sign-in continues in the browser, through the identity provider of the federated path taken; navigate the browser (top level) to GET /api/v3/auth/idp/initiate?request_uri=<request_uri> before expires_in elapses. The auth_session presented is spent.","enum":["insufficient_authorization","redirect_to_web"],"example":"insufficient_authorization"},"auth_session":{"type":["string","null"],"description":"403 insufficient_authorization only. The value the next request presents as auth_session. The value the request presented is no longer accepted.","example":"cX7Jb2kYw9TqL4mVn8RzP1sD6fGhA3eK0uWoZ5iBtCy"},"next":{"type":["string","null"],"description":"403 only. The step expected next:\n\n- credential: the first credential (the same answer for every caller)\n- tenant-selection: choose one of `tenants` with select-tenant\n- second-factor: one more proof, among `methods`\n- enrolment-required: an authenticator meeting `paths` must be enrolled first\n- setup: the initial setup of an identity whose credential another person set\n- recovery: a recovery code was accepted; only the enrolment of a new authenticator is admitted","enum":["credential","tenant-selection","second-factor","enrolment-required","setup","recovery"],"example":"credential"},"methods":{"type":"array","description":"403 only. The actions that take the next step: passkey (passkey-assertion with `public_key`), password, ldap-password, recovery-code, totp, select-tenant, passkey-registration (taken with passkey-registration-options, then passkey-registration), totp-enrolment (taken with totp-enrolment, then totp-enrolment-verification), totp-enrolment-verification, password-set, federated (with the idp_id of one federated path).","items":{"type":"string"}},"public_key":{"type":"null","$ref":"#/components/schemas/PublicKeyRequestOptions","description":"The WebAuthn PublicKeyCredentialRequestOptionsJSON of a passkey assertion, present when passkey is one of `methods`. Its challenge is bound to `auth_session`."},"tenants":{"type":"array","description":"next=tenant-selection only: the tenants the person may enter. Listed only after a proof that satisfies at least one of them.","items":{"$ref":"#/components/schemas/AuthorizationChallengeTenantOption"}},"paths":{"type":"array","description":"next=second-factor, enrolment-required or setup: the alternatives that would admit, each with the capability properties its authenticator must hold; a federated path names its identity provider.","items":{"$ref":"#/components/schemas/AuthorizationChallengePath"}},"public_key_creation":{"type":"null","$ref":"#/components/schemas/PublicKeyCreationOptions","description":"After passkey-registration-options: the WebAuthn PublicKeyCredentialCreationOptionsJSON of the new passkey, passed to navigator.credentials.create(). Its challenge is bound to the person and to `auth_session`."},"totp":{"type":"null","$ref":"#/components/schemas/AuthorizationChallengeTotpEnrolment","description":"After totp-enrolment: the secret of the authenticator app being enrolled, shown once. The enrolment completes with totp-enrolment-verification and the app's first code."},"recovery_codes":{"type":"array","description":"The person's first ten recovery codes, on the answer that follows the enrolment of their first passkey or authenticator app. A sign-in started from an activation or recovery link receives them on the answer that completes it, and they replace any codes held before. Shown once: store them offline. Each opens recovery once.","items":{"type":"string"}},"recoverability_recommended":{"type":["boolean","null"],"description":"On the code that completes the setup of a platform administrator: a second platform administrator and a break-glass account are recommended, so the platform stays recoverable if this passkey is lost. It blocks nothing.","example":true},"request_uri":{"type":["string","null"],"description":"403 redirect_to_web only. The single-use reference of this sign-in, presented once as the request_uri query parameter of GET /api/v3/auth/idp/initiate by a top-level browser navigation.","example":"urn:ietf:params:oauth:request_uri:d89yKbbI2vRXomFstVcabunGNHLZvtzOKeZp5xDbX90"},"expires_in":{"type":["integer","null"],"format":"int64","description":"403 redirect_to_web only. How long request_uri is honoured, in seconds: at most 60, never past the sign-in's own lifetime.","example":60,"maximum":60,"minimum":1},"passkey_available":{"type":["boolean","null"],"description":"403 insufficient_authorization only. Whether a passkey can be used on the address the console is open on. A passkey belongs to the deployment's Management host name, so on another address of the deployment (such as the host's IP address) this is false, no passkey method or path is offered, and the sign-in continues with a password and an authenticator app. The client shows this statement as it is; it never decides it from its own address. Absent when the answer makes no statement.","example":false},"passkey_unavailable_reason":{"type":["string","null"],"description":"Present only when passkey_available is false: why a passkey cannot be used here. origin-not-relying-party: the console is open on an address that is not the deployment's Management host name; open the console on that host name to use a passkey.","example":"origin-not-relying-party"}}},"AuthorizationChallengeTenantOption":{"type":"object","description":"A tenant the person may enter","properties":{"tenant_id":{"type":"string","format":"uuid","description":"The tenant, presented as tenant_id with select-tenant","example":"00000000-0000-0000-0000-000000000003"},"tenant_name":{"type":"string","description":"The tenant's display name","example":"Acme Corporation"}},"required":["tenant_id","tenant_name"]},"AuthorizationChallengeTotpEnrolment":{"type":"object","description":"An authenticator app being enrolled","properties":{"secret":{"type":"string","description":"The Base32 secret, for manual entry in the app","example":"JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP"},"otpauth_uri":{"type":"string","description":"The otpauth URI the console renders as a QR code","example":"otpauth://totp/ANKASecure:jane.doe%40acme.example?secret=JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP&issuer=ANKASecure&algorithm=SHA1&digits=6&period=30"}},"required":["otpauth_uri","secret"]},"PublicKeyCreationAuthenticatorSelection":{"type":"object","description":"WebAuthn AuthenticatorSelectionCriteria","properties":{"residentKey":{"type":"string","description":"Discoverable credential requirement","example":"required"},"requireResidentKey":{"type":"boolean","description":"WebAuthn Level 1 spelling of residentKey","example":true},"userVerification":{"type":"string","description":"User verification requirement","example":"required"}}},"PublicKeyCreationCredentialDescriptor":{"type":"object","description":"WebAuthn PublicKeyCredentialDescriptorJSON","properties":{"type":{"type":"string","description":"The credential type","example":"public-key"},"id":{"type":"string","description":"The credential id, base64url","example":"AV3kUf7zq1yJm0pXr9tB2cQe"},"transports":{"type":"array","description":"Transport hints reported at registration","items":{"type":"string"}}}},"PublicKeyCreationCredentialParameters":{"type":"object","description":"WebAuthn PublicKeyCredentialParameters","properties":{"type":{"type":"string","description":"The credential type","example":"public-key"},"alg":{"type":"integer","format":"int64","description":"COSE algorithm identifier (-7 ES256, -8 EdDSA, -257 RS256)","example":-7}}},"PublicKeyCreationExtensions":{"type":"object","description":"The client extensions requested","properties":{"credProps":{"type":"boolean","description":"Ask the client to report whether the credential is discoverable","example":true}}},"PublicKeyCreationOptions":{"type":"object","description":"WebAuthn PublicKeyCredentialCreationOptionsJSON","properties":{"challenge":{"type":"string","description":"The single-use challenge, base64url","example":"q4Zl8w0XQm1vYp2kR7tN3sBfHcJ6uD9eA5gLxVoWiE0"},"rp":{"$ref":"#/components/schemas/PublicKeyCreationRelyingParty","description":"The relying party"},"user":{"$ref":"#/components/schemas/PublicKeyCreationUser","description":"The user the passkey is created for"},"pubKeyCredParams":{"type":"array","description":"The accepted public key algorithms, preferred first","items":{"$ref":"#/components/schemas/PublicKeyCreationCredentialParameters"}},"timeout":{"type":"integer","format":"int64","description":"How long the ceremony may take, in milliseconds","example":300000},"excludeCredentials":{"type":"array","description":"The person's registered passkeys","items":{"$ref":"#/components/schemas/PublicKeyCreationCredentialDescriptor"}},"authenticatorSelection":{"$ref":"#/components/schemas/PublicKeyCreationAuthenticatorSelection","description":"The authenticator requirements"},"attestation":{"type":"string","description":"The attestation conveyance preference","enum":["none","direct"],"example":"none"},"extensions":{"$ref":"#/components/schemas/PublicKeyCreationExtensions","description":"The client extensions requested"}},"required":["attestation","authenticatorSelection","challenge","excludeCredentials","extensions","pubKeyCredParams","rp","timeout","user"]},"PublicKeyCreationRelyingParty":{"type":"object","description":"WebAuthn PublicKeyCredentialRpEntity","properties":{"id":{"type":"string","description":"The RP ID: the host of the Management hostname origin","example":"dev.ankatech.co"},"name":{"type":"string","description":"The RP name the authenticator shows","example":"ANKASecure"}}},"PublicKeyCreationUser":{"type":"object","description":"WebAuthn PublicKeyCredentialUserEntityJSON","properties":{"id":{"type":"string","description":"The user handle, base64url: random bytes generated once per person","example":"Xw3k9VhQ0mT2Lr5pYc8aN1sJ7dFb4GzKe6uHxWqRo0y"},"name":{"type":"string","description":"The sign-in address","example":"maria.lopez@acme.example"},"displayName":{"type":"string","description":"The person's display name","example":"Maria Lopez"}}},"PublicKeyRequestOptions":{"type":"object","description":"WebAuthn PublicKeyCredentialRequestOptionsJSON","properties":{"challenge":{"type":"string","description":"The single-use challenge, base64url","example":"q4Zl8w0XQm1vYp2kR7tN3sBfHcJ6uD9eA5gLxVoWiE0"},"rpId":{"type":"string","description":"The RP ID: the host of the Management hostname origin","example":"dev.ankatech.co"},"timeout":{"type":"integer","format":"int64","description":"How long the ceremony may take, in milliseconds","example":300000},"userVerification":{"type":"string","description":"The user verification requirement","enum":["required"],"example":"required"},"allowCredentials":{"type":"array","description":"Always empty: the authenticator offers its discoverable credentials, so the options name no account","items":{}}},"required":["allowCredentials","challenge","rpId","timeout","userVerification"]},"AccountAuthenticatorRecoveryCodesResponse":{"type":"object","description":"A new recovery code set, shown once; every earlier code is refused from now on","properties":{"recoveryCodes":{"type":"array","description":"The ten single-use recovery codes","items":{"type":"string","example":"7K3M-Q9TD-2XWH-B8NR-5FCJ-ZP4V"}}},"required":["recoveryCodes"]},"ResetPasswordRequest":{"type":"object","description":"Request to reset password using token","properties":{"token":{"type":"string","description":"The single-use reset token from the e-mailed link. Never longer than the longest token the issuer can mint; a longer value is 400 here and reaches no other service.","example":"eyJhbGciOiJSUzI1NiJ9...","maxLength":3673,"minLength":0},"newPassword":{"type":"string","format":"password","description":"New password. The password policy decides it: 15 to 128 characters, no composition rule, no common or compromised value, not containing the local part of the e-mail address. A password the policy refuses is 400 password-policy-violation naming the rule.","example":"harbour lantern quietly folding","maxLength":1024,"minLength":1}},"required":["newPassword","token"]},"ResetPasswordResponse":{"type":"object","description":"Response for successful password reset","properties":{"success":{"type":"boolean","description":"Success indicator"},"userId":{"type":"string","description":"User ID","example":"550e8400-e29b-41d4-a716-446655440000"},"email":{"type":"string","description":"Email address","example":"user@demo.company"}}},"ForgotPasswordRequest":{"type":"object","description":"Request for a password-reset link. Carries the e-mail address only: the tenant is resolved from the address server-side, and a request that names one is refused.","properties":{"email":{"type":"string","format":"email","description":"The address to send the reset link to. When it matches accounts in more than one tenant, one link is sent per account; when it matches none, nothing is sent. The response is the same either way.","example":"user@demo.company","maxLength":255,"minLength":1}},"required":["email"]},"ForgotPasswordResponse":{"type":"object","description":"Acknowledgement of a password-reset request. Identical for every outcome — a match in one tenant, in several, in none, or an internal failure.","properties":{"message":{"type":"string","description":"Generic message (prevents email enumeration)","example":"If the email address exists in our system, a password reset link has been sent"}}},"ChangePasswordRequest":{"type":"object","description":"Request to change the password of the account named by the access token. Carries no user, e-mail or tenant identifier: the subject is never negotiable.","properties":{"currentPassword":{"type":"string","format":"password","description":"The caller's current password. Required even though the session is already valid: reauthentication is what makes a stolen access token insufficient to take the account over permanently.","example":"Qu1etHarbour!Winter","minLength":1},"newPassword":{"type":"string","format":"password","description":"The replacement password. The password policy decides it: 15 to 128 characters, no composition rule, not a common or compromised password, not containing the local part of the e-mail address; a violation is refused with password-policy-violation naming the rule. It must also differ from the current password and the last 5, which is refused with the same body as a wrong current password.","example":"harbour lantern quietly folding","minLength":1}},"required":["currentPassword","newPassword"]},"AccountAuthenticatorTotpEnrolmentResponse":{"type":"object","description":"A started TOTP enrolment, shown once","properties":{"secret":{"type":"string","description":"The Base32 secret, for manual entry in the authenticator app","example":"JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP"},"otpauthUri":{"type":"string","description":"The otpauth URI the console renders as a QR code","example":"otpauth://totp/ANKASecure:jane.doe%40acme.example?secret=JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP&issuer=ANKASecure&algorithm=SHA1&digits=6&period=30"}},"required":["otpauthUri","secret"]},"AccountAuthenticatorTotpVerificationRequest":{"type":"object","description":"The first code of your authenticator app, which completes the TOTP enrolment","properties":{"otp":{"type":"string","description":"The one-time code the authenticator app shows now, six digits for a standard app. The same value the authorization challenge names `otp`.","example":"492039","maxLength":16,"minLength":0}},"required":["otp"]},"AccountAuthenticatorTotpVerificationResponse":{"type":"object","description":"Whether the TOTP enrolment was completed, with the first recovery codes when issued","properties":{"verified":{"type":"boolean","description":"true when the code was valid and the factor is now enabled; false when the code was refused, and the enrolment may be retried with the next code","example":true},"recoveryCodes":{"type":"array","description":"Your first ten single-use recovery codes, shown once; null when you already held a set or the code was refused","items":{"type":"string","example":"7K3M-Q9TD-2XWH-B8NR-5FCJ-ZP4V"}}},"required":["verified"]},"AccountAuthenticatorPasskeyRegistrationRequest":{"type":"object","description":"A passkey registration made with the options of createPasskeyRegistrationOptions","properties":{"displayName":{"type":"string","description":"The name you give the passkey, at most 100 characters","example":"Work laptop","maxLength":100,"minLength":0,"pattern":"^\\P{Cc}*$"},"credential":{"$ref":"#/components/schemas/AccountPasskeyRegistrationCredential","description":"The WebAuthn registration response"}},"required":["credential","displayName"]},"AccountPasskeyRegistrationCredential":{"type":"object","description":"WebAuthn RegistrationResponseJSON members","properties":{"id":{"type":"string","description":"The credential id, base64url","example":"AV3kUf7zq1yJm0pXr9tB2cQe","maxLength":1366,"minLength":0},"clientDataJSON":{"type":"string","description":"response.clientDataJSON, base64url","example":"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoicTRabDh3MFhRbTF2WXAya1I3dE4zc0JmSGNKNnVEOWVBNWdMeFZvV2lFMCIsIm9yaWdpbiI6Imh0dHBzOi8vZGV2LmFua2F0ZWNoLmNvIn0","maxLength":8192,"minLength":0},"attestationObject":{"type":"string","description":"response.attestationObject, base64url","example":"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYdKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBdAAAAAA","maxLength":65536,"minLength":0},"transports":{"type":"array","description":"The transport hints the browser reported, at most 8","items":{"type":"string","example":"internal","maxLength":32,"minLength":0,"pattern":"^[a-z-]+$"},"maxItems":8,"minItems":0},"credPropsRk":{"type":["boolean","null"],"description":"clientExtensionResults.credProps.rk; null when the browser did not report it","example":true}},"required":["attestationObject","clientDataJSON","id"]},"AccountAuthenticatorPasskeyRegistrationResponse":{"type":"object","description":"A stored passkey, with the first recovery codes when it is your first strong factor","properties":{"authenticatorId":{"type":"string","format":"uuid","description":"The opaque authenticator id; never the WebAuthn credential id","example":"7c1e4b2a-9d3f-4e6a-8b5c-2f1d0e9a7b64"},"displayName":{"type":"string","description":"The name you gave the passkey","example":"Work laptop"},"properties":{"$ref":"#/components/schemas/AccountAuthenticatorProperties","description":"The passkey's capability properties"},"recoveryCodes":{"type":"array","description":"Your first ten single-use recovery codes, shown once; null when you already held a set","items":{"type":"string","example":"7K3M-Q9TD-2XWH-B8NR-5FCJ-ZP4V"}}},"required":["authenticatorId","displayName","properties"]},"AccountAuthenticatorProperties":{"type":"object","description":"Capability properties of an authenticator; a property is true only when it is established","properties":{"phishingResistant":{"type":"boolean","description":"The proof is bound to the origin of the relying party (every passkey; never TOTP)","example":true},"userVerifying":{"type":"boolean","description":"The authenticator verifies the person on every use (WebAuthn UV flag)","example":true},"syncable":{"type":"boolean","description":"The credential may be synced to the person's other devices (WebAuthn BE flag set)","example":true},"deviceBound":{"type":"boolean","description":"The credential cannot leave the authenticator it was created on (BE flag clear)","example":false},"hardwareBacked":{"type":"boolean","description":"A verified attestation states a hardware key store","example":false},"attested":{"type":"boolean","description":"The attestation verified against a trust anchor of the loaded FIDO metadata","example":false},"discoverable":{"type":["boolean","null"],"description":"The credential is discoverable (credProps rk); null when the browser did not report it","example":true}},"required":["attested","deviceBound","hardwareBacked","phishingResistant","syncable","userVerifying"]},"AccountAuthenticatorUpdateRequest":{"type":"object","description":"The new name of one of your passkeys","properties":{"displayName":{"type":"string","description":"The new name of the passkey, at most 100 characters","example":"Security key in the office safe","maxLength":100,"minLength":0,"pattern":"^\\P{Cc}*$"}},"required":["displayName"]},"AccountAuthenticatorResponse":{"type":"object","description":"One of the person's authenticators. Never carries a credential id, a public key, an attestation statement or a secret.","properties":{"authenticatorId":{"type":"string","format":"uuid","description":"The opaque authenticator id the platform assigned; never the WebAuthn credential id","example":"7c1e4b2a-9d3f-4e6a-8b5c-2f1d0e9a7b64"},"kind":{"type":"string","description":"The authenticator kind","enum":["PASSKEY","TOTP"],"example":"PASSKEY"},"displayName":{"type":["string","null"],"description":"The name the person gave the passkey; null for TOTP","example":"Work laptop"},"createdAt":{"type":"string","format":"date-time","description":"When it was registered (TOTP: when its enrolment completed)","example":"2026-10-17T14:20:00Z"},"lastUsedAt":{"type":["string","null"],"format":"date-time","description":"When it last signed in; null when it never has","example":"2026-10-17T15:02:41Z"},"properties":{"$ref":"#/components/schemas/AccountAuthenticatorProperties","description":"The capability properties"},"rpId":{"type":["string","null"],"description":"Passkeys only: the host name the passkey was registered for (its WebAuthn relying party identifier). Absent for an authenticator app, and when the account service did not state it.","example":"admin.example.com"},"registeredForCurrentRpId":{"type":["boolean","null"],"description":"Passkeys only: whether the passkey was registered for this deployment's current host name. A passkey registered for another host name (the deployment's host name changed since) is listed, can be renamed or removed, and is never offered at sign-in: a browser uses a passkey only on the host name it was registered for. Absent for an authenticator app, and when the account service did not state it: absence never means false.","example":true}},"required":["authenticatorId","createdAt","kind","properties"]},"BlacklistResponse":{"type":"object","description":"Response containing blacklisted token JTIs","properties":{"total":{"type":"integer","format":"int32","description":"Number of tokens in this response","example":42},"tokens":{"type":"array","description":"Set of blacklisted token JTIs","items":{"type":"string"},"uniqueItems":true},"truncated":{"type":"boolean","description":"True when results were truncated at 5000 entries","example":false}}},"HumanSessionResponse":{"type":"object","description":"One live sign-in session of a person. A session begins at a sign-in, keeps its identifier across every refresh and tenant switch, and ends when it is revoked or logged out, when it stays inactive for longer than its inactivity window, or at its absolute expiry. No token is shown.","properties":{"sessionId":{"type":"string","format":"uuid","description":"The session's identifier. The access tokens of the session carry it as the `sid` claim.","example":"4f1d2c3b-9a8e-4d7c-b6a5-0e1f2a3b4c5d"},"tenantId":{"type":"string","format":"uuid","description":"The tenant the session is currently in: the tenant of its last sign-in, tenant selection or tenant switch.","example":"00000000-0000-0000-0000-000000000010"},"authTime":{"type":["string","null"],"format":"date-time","description":"When the person authenticated (RFC 3339). A refresh or a tenant switch never changes it. Null when the authentication time is unknown: a session begun by a refresh token issued before the platform kept sessions.","example":"2026-10-16T08:12:41Z"},"authority":{"type":["string","null"],"description":"Who authenticated the person: `LOCAL` for a password sign-in to the platform, otherwise the identifier of the identity provider. In a tenant administrator's list it is null when the session was federated through an identity provider that is not one of the tenant's (neither the tenant's own provider nor the deployment's provider the tenant inherits), as when a session begun in another tenant switched into this one: the other tenant's provider is never shown.","example":"LOCAL"},"idpKind":{"type":["string","null"],"description":"The kind of the identity provider that authenticated the person. Null for a local sign-in, when the kind is unknown, or when `authority` is withheld.","enum":["OIDC","SAML","LDAP"],"example":"OIDC"},"methods":{"type":"array","description":"The authentication method references of the sign-in (RFC 8176), as the session's access tokens carry them in `amr`: `pwd` for a password; what the identity provider asserted for a federated sign-in. Empty when none was asserted.","items":{"type":"string","example":"pwd"}},"createdAt":{"type":"string","format":"date-time","description":"When the session began (RFC 3339).","example":"2026-10-16T08:12:41Z"},"lastActivityAt":{"type":"string","format":"date-time","description":"The session's last sign-in or refresh (RFC 3339).","example":"2026-10-16T09:40:05Z"},"absoluteExpiresAt":{"type":"string","format":"date-time","description":"The instant after which the session ends whatever its activity (RFC 3339): its beginning plus the configured absolute session lifetime. It never moves.","example":"2026-10-16T20:12:41Z"},"rememberMe":{"type":"boolean","description":"Whether the session is remembered: its refresh cookie is persistent and the longer Remember Me inactivity window applies, never past the absolute expiry.","example":false},"clientAddress":{"type":["string","null"],"description":"The client address the sign-in came from, as the platform resolved it. Null when none was recorded.","example":"203.0.113.24"},"userAgent":{"type":["string","null"],"description":"The user agent of the sign-in. Null when none was recorded.","example":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"},"current":{"type":"boolean","description":"Whether the request that listed the session came from it.","example":true},"breakGlass":{"type":"boolean","description":"Whether the session is a break-glass session: opened through the emergency-access entry by a designated break-glass member with a security key or a device-bound passkey. Its access tokens carry the `break_glass` claim; it is never remembered and cannot switch tenant.","example":false}},"required":["absoluteExpiresAt","authTime","authority","breakGlass","clientAddress","createdAt","current","idpKind","lastActivityAt","methods","rememberMe","sessionId","tenantId","userAgent"]},"HumanSessionsResponse":{"type":"object","description":"A list of live sign-in sessions","properties":{"sessions":{"type":"array","description":"The live sessions, newest first. Empty when there is none.","items":{"$ref":"#/components/schemas/HumanSessionResponse"}}},"required":["sessions"]},"AccountMembershipsResponse":{"type":"object","description":"The tenants the signed-in person may switch to with a refresh_token grant carrying tenant_id","properties":{"memberships":{"type":"array","description":"The person's ACTIVE memberships of tenants whose state permits sign-in and whose single sign-on policy admits the way this session was authenticated, ordered by tenant id. The tenant the session is signed in to is included. Empty when there is none.","items":{"$ref":"#/components/schemas/TenantMatch"}}},"required":["memberships"]},"TenantMatch":{"type":"object","description":"One membership the authenticated identity may sign into","properties":{"accountType":{"type":"string","description":"PLATFORM_ADMIN for the membership of the platform tenant, TENANT_ADMIN for any other tenant","enum":["PLATFORM_ADMIN","TENANT_ADMIN"],"example":"TENANT_ADMIN"},"tenantId":{"type":"string","description":"Tenant UUID","example":"00000000-0000-0000-0000-000000000003"},"tenantName":{"type":"string","description":"Human-readable tenant name for the picker UI","example":"ACME Corporation"}},"required":["accountType","tenantId","tenantName"]},"AccountAuthenticatorsResponse":{"type":"object","description":"The signed-in person's authenticators","properties":{"authenticators":{"type":"array","description":"The passkeys first, then the TOTP factor, each group oldest first. Empty when the person holds none.","items":{"$ref":"#/components/schemas/AccountAuthenticatorResponse"}},"passwordHeld":{"type":["boolean","null"],"description":"Whether the person holds a password, the one deleteAccountPassword removes. Absent when the account service did not state it: absence never means false.","example":true},"totpEnrolmentAdmitted":{"type":["boolean","null"],"description":"Whether the person's account admits an authenticator app at all (createTotpEnrolment). False when no tenant the person enters with their own authenticators, nor the platform for a platform identity, admits password + TOTP. It does not say whether one is already held (the authenticators list does): a second enrolment is refused with 409. Absent when the account service did not state it (a release before this member): absence never means false.","example":true},"passkeyEnrolmentAdmitted":{"type":["boolean","null"],"description":"Whether the person's account admits a passkey enrolment (createPasskeyRegistrationOptions): true when a tenant the person enters with their own authenticators, or the platform for a platform administrator, accepts a passkey. False when every tenant of the person is entered through its identity provider only, so a passkey would serve no sign-in. Absent when the account service did not state it (a release before this member): absence never means false.","example":true}},"required":["authenticators"]},"AuthorizationServerMetadata":{"type":"object","description":"OAuth 2.0 authorization server metadata (RFC 8414 §2). The members a client of this service needs: the issuer its tokens carry, where to request a token, where to fetch the keys that verify it, and the algorithms a DPoP proof may use.","properties":{"issuer":{"type":"string","description":"The issuer identifier: the `iss` claim of every access token this service issues. It names the platform's authorization server and is the same in every deployment; compare a token's `iss` with it, not with the URL this document was read from.","example":"https://auth.ankatech.co"},"token_endpoint":{"type":"string","description":"The public URL of the token endpoint. A DPoP proof sent to it names this URL as its `htu`.","example":"https://dev.ankatech.co/api/v3/auth/token"},"jwks_uri":{"type":"string","description":"The public URL of the JWK Set holding the keys that verify this service's access tokens.","example":"https://dev.ankatech.co/api/v3/auth/.well-known/jwks.json"},"dpop_signing_alg_values_supported":{"type":"array","description":"The JWS algorithms a DPoP proof may be signed with (RFC 9449 §5.1) when an administrative workload requests a DPoP-bound token.","items":{"type":"string","enum":["ES256","ES384","PS256","RS256"],"example":"ES256"}}},"required":["dpop_signing_alg_values_supported","issuer","jwks_uri","token_endpoint"]}},"securitySchemes":{"bearerAuth":{"type":"http","description":"JWT token obtained from POST /api/v3/auth/token (grant_type: authorization_code, client_credentials or refresh_token)","scheme":"bearer","bearerFormat":"JWT"}}}}