{"openapi":"3.0.3","info":{"title":"@faablecloud/auth","description":"Auth Platform made by Faable. Manage Users and Roles","version":"2.104.1","license":{"name":"private","url":"https://faable.com/docs/platform/privacy-policy"},"termsOfService":"https://faable.com/docs/platform/terms-of-service"},"components":{"securitySchemes":{"apikey":{"type":"apiKey","in":"query","name":"api_key"},"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"},"faableauth":{"type":"oauth2","flows":{"authorizationCode":{"authorizationUrl":"http://localhost:4500/authorize","tokenUrl":"http://localhost:4500/oauth/token","scopes":{"base":"openid profile email offline_access"}}}}},"schemas":{"AuthAccount":{"type":"object","required":["id","name","domain","slug","callback_hostnames","token_signature","token_signing_alg","enabled_locales","notification_settings","createdAt"],"properties":{"id":{"type":"string","description":"AuthAccount ID"},"name":{"type":"string"},"domain":{"type":"string"},"slug":{"type":"string"},"logo_src":{"type":"string","nullable":true},"icon_src":{"type":"string","nullable":true},"callback_hostnames":{"type":"array","items":{"type":"string"}},"token_signature":{"type":"string","description":"Shared HMAC secret. Used when token_signing_alg is an HS* algorithm; ignored for RS*/ES*/PS*."},"token_signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign tokens issued by this Account. Today only RS256 is implemented."},"default_connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"enabled_locales":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"},"description":"Free-form labels on the account. The dashboard stores the environment as an `env:<name>` tag (e.g. `env:production`, `env:staging`, `env:test`)."},"team":{"type":"string","nullable":true},"notification_settings":{"type":"object","required":["welcome_email_enabled","verify_email_auto_send"],"properties":{"welcome_email_enabled":{"type":"boolean","description":"Send the built-in welcome email on user.created","default":false},"verify_email_auto_send":{"type":"boolean","description":"Send a verification email automatically when a user is created with `email_verified=false` and an email address. The link in the email lands on `GET /verify-email?ticket=...` and flips `email_verified=true` with `email_verified_method=verification_flow`. When `false`, verification emails must be requested explicitly via `POST /user/:id/verify-email/start`.","default":false}},"additionalProperties":false},"email_reply_to":{"type":"string","format":"email","maxLength":320,"description":"Reply-To on every email this tenant sends to its users. Absent = no Reply-To, so a reply goes to the From address — which is a `no-reply@` mailbox nobody reads. Set it to your own support address if you want your users to be able to answer.","nullable":true},"email_from_name":{"type":"string","maxLength":100,"description":"Display name on every email this tenant sends to its users. Absent = the account `name`.","nullable":true},"email_from_address":{"type":"string","description":"Sender address of every email this tenant sends to its users. Absent = the platform default. Set by Faable only.","nullable":true},"welcome_email":{"type":"object","properties":{"cta_url":{"type":"string","format":"uri","maxLength":500,"description":"Where the welcome button sends the user. Defaults to `https://<first callback hostname>`. Point it at your app (not your marketing site) so a fresh user lands somewhere useful.","nullable":true},"body":{"type":"string","maxLength":600,"description":"Replaces the default one-line body (\"Your account on … is ready to go.\"). Plain text, sent verbatim in every enabled locale.","nullable":true},"social_links":{"type":"array","items":{"type":"object","required":["label","url"],"properties":{"label":{"type":"string","minLength":1,"maxLength":40},"url":{"type":"string","format":"uri","maxLength":500}},"additionalProperties":false},"maxItems":6,"description":"Rendered as a \"Follow <account>\" block after the button — GitHub, LinkedIn, X, YouTube… Empty or absent hides the block.","nullable":true}},"additionalProperties":false,"nullable":true},"email_change_verification_mode":{"anyOf":[{"type":"string","enum":["new_only"]},{"type":"string","enum":["old_and_new"]}],"description":"Policy for the user email-change flow. `new_only` (default) sends a single confirmation link to the new email. `old_and_new` requires the user to also click a link sent to the previous email before the swap takes effect — stricter, useful for tenants with higher-risk users."},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]}],"description":"Controls what happens to `user.email` on subsequent OAuth/federated logins when the user previously changed their email manually through this auth server (i.e. `user.email_change_locked_at` is set).\n\n- `preserve_manual` (default): the manually-set email wins. The federated provider's email is ignored on re-sync; `email` and `email_verified` are not touched. The identity link stays valid via `provider_user_id`, so the user can still log in with Google/etc.\n- `always_sync`: the federated provider's email is always written back, overwriting any manual change. Useful for tenants whose source of truth for identity is the IdP (corporate SSO, etc.)."},"login_methods":{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"login_flow":{"type":"string","description":"The login flow bound to this account (`loginflow_xxx`). Absent = the flow compiled from the settings. A Client may bind its own.","nullable":true},"mfa_policy":{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},"recovery_channels":{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},"default_country_iso":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country assumed for phone numbers written without an international prefix (`636647460` → `+34…` with `ES`). Needed before SMS can reach anyone: most people type their number without a prefix.","nullable":true},"webauthn_rp_id":{"type":"string","description":"WebAuthn Relying Party ID for this tenant's passkeys. Defaults to the account domain. Set it to a registrable suffix you own (e.g. `acme.com`) when the login screen is served from more than one host — the RP ID is frozen into every credential at registration, so changing it afterwards invalidates every passkey already enrolled.","nullable":true},"logout_confirm_required":{"type":"boolean","description":"When true, `/logout` asks the End-User to confirm before ending the session unless the request carries a verified `id_token_hint`. Off by default — a plain link to `/logout` is otherwise enough to sign anyone out (logout CSRF)."},"dynamic_client_registration_enabled":{"type":"boolean","description":"When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`."},"oauth21_enabled":{"type":"boolean","description":"When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later."},"oauth_link_verified_email":{"type":"boolean","description":"When true, signing in with Google, GitHub, GitLab or Apple for the first time attaches that provider to the existing user with the same email instead of creating a second user — only when the provider attests the email as verified, exactly one user of the tenant has that email verified, that user is not suspended and has no other identity of the same connection. Off by default."},"client_id_metadata_documents_enabled":{"type":"boolean","description":"When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default."},"canonical_host":{"type":"string","description":"The one host this tenant's OIDC issuer and discovery document are always minted with, regardless of which host actually served the request — must be the account's own platform host or one of its verified custom domains. `/authorize` and `/logout` 302 here from any other host of the tenant. Unset (the default): every URL uses whatever host served the request, as before this field existed. Changing it can invalidate every WebAuthn passkey already enrolled unless `webauthn_rp_id` already covers the new host.","nullable":true},"createdAt":{"type":"string","description":"AuthAccount creation date"},"updatedAt":{"type":"string","description":"AuthAccount updated date"}},"description":"AuthAccount"},"TokenSigningAlg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign tokens issued by this Account. Today only RS256 is implemented."},"AccountNotificationSettings":{"type":"object","required":["welcome_email_enabled","verify_email_auto_send"],"properties":{"welcome_email_enabled":{"type":"boolean","description":"Send the built-in welcome email on user.created","default":false},"verify_email_auto_send":{"type":"boolean","description":"Send a verification email automatically when a user is created with `email_verified=false` and an email address. The link in the email lands on `GET /verify-email?ticket=...` and flips `email_verified=true` with `email_verified_method=verification_flow`. When `false`, verification emails must be requested explicitly via `POST /user/:id/verify-email/start`.","default":false}},"additionalProperties":false},"EmailChangeVerificationMode":{"anyOf":[{"type":"string","enum":["new_only"]},{"type":"string","enum":["old_and_new"]}],"description":"Policy for the user email-change flow. `new_only` (default) sends a single confirmation link to the new email. `old_and_new` requires the user to also click a link sent to the previous email before the swap takes effect — stricter, useful for tenants with higher-risk users."},"Connection":{"type":"object","required":["id","connection_name","connection_type","authorize_url","token_url","userinfo_url","client_id","client_secret","issuer","jwks_url","response_type","enabled","enabled_clients","scope","authorize_params","is_using_default_credentials","account","createdAt"],"properties":{"id":{"type":"string","description":"Connection ID"},"connection_name":{"type":"string"},"connection_type":{"enum":["database","custom","github","figma","google_oauth2","microsoft","oidc","apple","facebook","linkedin","discord","gitlab","slack","x","twitch","bitbucket","spotify","passwordless_email"]},"authorize_url":{"type":"string","default":null},"token_url":{"type":"string","default":null},"userinfo_url":{"type":"string","default":null},"client_id":{"type":"string","default":null},"client_secret":{"type":"string","default":null},"issuer":{"type":"string","default":null},"jwks_url":{"type":"string","default":null},"response_type":{"type":"string","default":null},"enabled":{"type":"boolean"},"organization":{"type":"string","description":"The Organization whose identity provider this is (`organization_…`), or `null`. Only `oidc` connections. Reached by home realm discovery from the organization's verified email domains, and only accepts emails of those domains. Pro plan.","nullable":true},"enabled_clients":{"type":"array","items":{"type":"string"},"default":null,"nullable":true},"scope":{"type":"array","items":{"type":"string"},"default":[]},"authorize_params":{"type":"object","description":"Authorize params","default":{},"additionalProperties":{"type":"string"}},"provider_options":{"type":"object","properties":{"team_id":{"type":"string","description":"Apple: the Apple Developer Team ID (10 characters)."},"key_id":{"type":"string","description":"Apple: the Key ID of the Sign in with Apple private key."}},"additionalProperties":false,"description":"Provider-specific settings beyond client_id/client_secret. Only `apple` uses them."},"claims_mapping":{"type":"object","additionalProperties":{"type":"string"}},"is_using_default_credentials":{"type":"boolean","readOnly":true,"description":"Derived: true when the connection_type has shared Faable defaults AND the tenant has not set its own client_id/client_secret. Read-only — set by the server on every read."},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]}],"description":"Per-connection override of `Account.email_oauth_sync_policy`. When set on a Connection, takes precedence over the Account-level setting for logins through this IdP. When unset (default), the Account-level policy applies. Use this to allow `always_sync` for a trusted IdP (e.g. corporate SSO) while keeping `preserve_manual` for others within the same tenant."},"password_policy":{"type":"object","required":["level"],"properties":{"level":{"anyOf":[{"type":"string","enum":["none"]},{"type":"string","enum":["low"]},{"type":"string","enum":["fair"]},{"type":"string","enum":["good"]},{"type":"string","enum":["excellent"]}]},"min_length":{"type":"integer","minimum":1,"maximum":128},"require_lowercase":{"type":"boolean"},"require_uppercase":{"type":"boolean"},"require_number":{"type":"boolean"},"require_special":{"type":"boolean"}},"description":"Password policy enforced when writing a password through this database connection (password reset and admin set-password). `level` is an Auth0-style preset (none/low/fair/good/excellent); the dashboard expands it into the explicit rule fields when saving. The rule fields override the preset baseline. When unset on the connection, the server applies the `good` default. Does not affect login."},"login_identifier":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["username"]},{"type":"string","enum":["email_or_username"]}],"description":"What users of this database connection sign in with: `email`, `username`, or `email_or_username` (email first, then username). The login looks up only the matching field, and the hosted login screen asks for it — validating the address format under `email`. New database connections default to `email`; a connection created before this setting existed behaves as `email_or_username`. Only meaningful on `database` connections."},"disable_signup":{"type":"boolean","description":"When true, the public self-service signup endpoint (`POST /dbconnections/signup`) rejects new registrations against this database connection with `403 signup_disabled`. Defaults to false (signup allowed). Only meaningful on `database` connections; mirrors Auth0 `disable_signup`."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ConnectionMetadata","default":{}},"createdAt":{"type":"string","description":"Connection creation date"},"updatedAt":{"type":"string","description":"Connection updated date"}},"description":"Connection"},"ConnectionMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ConnectionMetadata","default":{}},"ConnectionCreate":{"type":"object","required":["connection_name","connection_type"],"properties":{"connection_name":{"type":"string"},"connection_type":{"enum":["database","custom","github","figma","google_oauth2","microsoft","oidc","apple","facebook","linkedin","discord","gitlab","slack","x","twitch","bitbucket","spotify","passwordless_email"]},"authorize_url":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string"},"issuer":{"type":"string"},"jwks_url":{"type":"string"},"token_url":{"type":"string"},"userinfo_url":{"type":"string"},"organization":{"type":"string","description":"The Organization whose identity provider this is (`organization_…`), or `null`. Only `oidc` connections. Reached by home realm discovery from the organization's verified email domains, and only accepts emails of those domains. Pro plan."},"scope":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"claims_mapping":{"type":"object","additionalProperties":{"type":"string"}},"authorize_params":{"type":"object","description":"Authorize params","default":{},"additionalProperties":{"type":"string"}},"provider_options":{"type":"object","properties":{"team_id":{"type":"string","description":"Apple: the Apple Developer Team ID (10 characters)."},"key_id":{"type":"string","description":"Apple: the Key ID of the Sign in with Apple private key."}},"additionalProperties":false,"description":"Provider-specific settings beyond client_id/client_secret. Only `apple` uses them."},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]}],"description":"Controls what happens to `user.email` on subsequent OAuth/federated logins when the user previously changed their email manually through this auth server (i.e. `user.email_change_locked_at` is set).\n\n- `preserve_manual` (default): the manually-set email wins. The federated provider's email is ignored on re-sync; `email` and `email_verified` are not touched. The identity link stays valid via `provider_user_id`, so the user can still log in with Google/etc.\n- `always_sync`: the federated provider's email is always written back, overwriting any manual change. Useful for tenants whose source of truth for identity is the IdP (corporate SSO, etc.)."},"password_policy":{"type":"object","required":["level"],"properties":{"level":{"anyOf":[{"type":"string","enum":["none"]},{"type":"string","enum":["low"]},{"type":"string","enum":["fair"]},{"type":"string","enum":["good"]},{"type":"string","enum":["excellent"]}]},"min_length":{"type":"integer","minimum":1,"maximum":128},"require_lowercase":{"type":"boolean"},"require_uppercase":{"type":"boolean"},"require_number":{"type":"boolean"},"require_special":{"type":"boolean"}},"description":"Password policy enforced when writing a password through this database connection (password reset and admin set-password). `level` is an Auth0-style preset (none/low/fair/good/excellent); the dashboard expands it into the explicit rule fields when saving. The rule fields override the preset baseline. When unset on the connection, the server applies the `good` default. Does not affect login."},"disable_signup":{"type":"boolean"},"login_identifier":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["username"]},{"type":"string","enum":["email_or_username"]}],"description":"What users of this database connection sign in with: `email`, `username`, or `email_or_username` (email first, then username). The login looks up only the matching field, and the hosted login screen asks for it — validating the address format under `email`. New database connections default to `email`; a connection created before this setting existed behaves as `email_or_username`. Only meaningful on `database` connections."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"ConnectionCreate","additionalProperties":false},"ConnectionMetadataCreate":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"},"ConnectionUpdate":{"type":"object","properties":{"connection_name":{"type":"string"},"enabled":{"type":"boolean"},"enabled_clients":{"type":"array","items":{"type":"string"}},"scope":{"type":"array","items":{"type":"string"}},"client_id":{"type":"string"},"client_secret":{"type":"string"},"issuer":{"type":"string"},"authorize_url":{"type":"string"},"token_url":{"type":"string"},"userinfo_url":{"type":"string"},"jwks_url":{"type":"string"},"response_type":{"type":"string"},"authorize_params":{"type":"object","description":"Authorize params","default":{},"additionalProperties":{"type":"string"}},"provider_options":{"type":"object","properties":{"team_id":{"type":"string","description":"Apple: the Apple Developer Team ID (10 characters)."},"key_id":{"type":"string","description":"Apple: the Key ID of the Sign in with Apple private key."}},"additionalProperties":false,"description":"Provider-specific settings beyond client_id/client_secret. Only `apple` uses them."},"claims_mapping":{"type":"object","additionalProperties":{"type":"string"}},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]},{"type":"null"}]},"password_policy":{"anyOf":[{"type":"object","required":["level"],"properties":{"level":{"anyOf":[{"type":"string","enum":["none"]},{"type":"string","enum":["low"]},{"type":"string","enum":["fair"]},{"type":"string","enum":["good"]},{"type":"string","enum":["excellent"]}]},"min_length":{"type":"integer","minimum":1,"maximum":128},"require_lowercase":{"type":"boolean"},"require_uppercase":{"type":"boolean"},"require_number":{"type":"boolean"},"require_special":{"type":"boolean"}},"description":"Password policy enforced when writing a password through this database connection (password reset and admin set-password). `level` is an Auth0-style preset (none/low/fair/good/excellent); the dashboard expands it into the explicit rule fields when saving. The rule fields override the preset baseline. When unset on the connection, the server applies the `good` default. Does not affect login."},{"type":"null"}]},"disable_signup":{"type":"boolean"},"login_identifier":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["username"]},{"type":"string","enum":["email_or_username"]}],"description":"What users of this database connection sign in with: `email`, `username`, or `email_or_username` (email first, then username). The login looks up only the matching field, and the hosted login screen asks for it — validating the address format under `email`. New database connections default to `email`; a connection created before this setting existed behaves as `email_or_username`. Only meaningful on `database` connections."},"organization":{"type":"string","nullable":true}},"description":"Partial update for a Connection. Only the supplied fields are modified. `connection_type` is intentionally excluded — changing it would orphan every identity already linked to this connection.","additionalProperties":false},"ConnectionTypeInfo":{"type":"object","required":["type","label","icon","category","configured_provider","supports_default_credentials","default_scope"],"properties":{"type":{"enum":["database","custom","github","figma","google_oauth2","microsoft","oidc","apple","facebook","linkedin","discord","gitlab","slack","x","twitch","bitbucket","spotify","passwordless_email"]},"label":{"type":"string","description":"Brand name for this provider. Not translatable — \"GitHub\" is \"GitHub\" in every locale."},"icon":{"type":"string","description":"Stable icon slug (e.g. `github`, `google`, `microsoft`). Clients map it to their own icon set; it is not a URL."},"category":{"type":"string","enum":["database","social","passwordless","oidc"],"description":"Grouping this type belongs to. Matches the `?query=category:<value>` filter on GET /connection."},"configured_provider":{"type":"boolean","description":"The server preconfigures this provider's OAuth2 endpoints, so a tenant only supplies credentials. False for `custom` (bring your own URLs) and for non-OAuth types."},"supports_default_credentials":{"type":"boolean","description":"Faable ships a shared OAuth app for this provider, so a tenant can enable it without registering anything of its own."},"default_scope":{"type":"array","items":{"type":"string"},"description":"Scopes requested from the provider when a connection of this type stores none of its own. Empty for types without an upstream identity provider."},"docs":{"type":"object","required":["label","url"],"properties":{"label":{"type":"string"},"url":{"type":"string"}},"description":"Provider documentation for tenants registering their own OAuth app."},"passwordless":{"type":"object","required":["code_length","code_ttl_seconds","link_ttl_seconds","resend_interval_seconds"],"properties":{"code_length":{"type":"integer","description":"Digits in an emailed code."},"code_ttl_seconds":{"type":"integer","description":"Seconds an emailed code stays valid."},"link_ttl_seconds":{"type":"integer","description":"Seconds a magic link stays valid."},"resend_interval_seconds":{"type":"integer","description":"One email per address in this window: a start inside it sends nothing new."}},"additionalProperties":false,"description":"How a passwordless type behaves. Present only on types of the `passwordless` category."},"credential_fields":{"type":"array","items":{"type":"object","required":["key","label"],"properties":{"key":{"type":"string","description":"Connection field this input writes, as a dotted path (`client_id`, `provider_options.team_id`)."},"label":{"type":"string"},"description":{"type":"string"},"secret":{"type":"boolean","description":"A credential: render it as a password input."},"multiline":{"type":"boolean","description":"A PEM key, not a one-line value."}},"additionalProperties":false},"description":"The credentials a tenant fills in for its own app, when they are more than a client id and a secret (Apple). Absent means client id + secret."}},"description":"Presentation metadata for one connection type. Contains no endpoint URLs and no credentials.","additionalProperties":false},"LoginOptionMethod":{"anyOf":[{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["passkey"]}},"additionalProperties":false},{"type":"object","required":["kind","id","connection_type","connection_name","password_policy"],"properties":{"kind":{"type":"string","enum":["connection"]},"id":{"type":"string"},"connection_type":{"type":"string"},"connection_name":{"type":"string"},"password_policy":{"type":"object","required":["level"],"properties":{"level":{"anyOf":[{"type":"string","enum":["none"]},{"type":"string","enum":["low"]},{"type":"string","enum":["fair"]},{"type":"string","enum":["good"]},{"type":"string","enum":["excellent"]}]},"min_length":{"type":"integer","minimum":1,"maximum":128},"require_lowercase":{"type":"boolean"},"require_uppercase":{"type":"boolean"},"require_number":{"type":"boolean"},"require_special":{"type":"boolean"}},"description":"Password policy enforced when writing a password through this database connection (password reset and admin set-password). `level` is an Auth0-style preset (none/low/fair/good/excellent); the dashboard expands it into the explicit rule fields when saving. The rule fields override the preset baseline. When unset on the connection, the server applies the `good` default. Does not affect login."},"login_identifier":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["username"]},{"type":"string","enum":["email_or_username"]}],"description":"What users of this database connection sign in with: `email`, `username`, or `email_or_username` (email first, then username). The login looks up only the matching field, and the hosted login screen asks for it — validating the address format under `email`. New database connections default to `email`; a connection created before this setting existed behaves as `email_or_username`. Only meaningful on `database` connections."}},"additionalProperties":false}],"description":"One method the login screen should offer. Discriminated by `kind`. Connection methods carry only what a login screen needs — never credentials, endpoints or the rest of the Connection document."},"Client":{"type":"object","required":["id","name","description","client_id","client_secret","callbacks","logout_urls","web_origins","refresh_token","account","createdAt"],"properties":{"id":{"type":"string","description":"Client ID"},"name":{"type":"string"},"description":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string"},"callbacks":{"type":"array","items":{"type":"string"}},"logout_urls":{"type":"array","items":{"type":"string"}},"web_origins":{"type":"array","items":{"type":"string"},"description":"Origins allowed to make cross-origin (CORS) requests to the token and passwordless endpoints. Empty = any origin."},"refresh_token":{"type":"object","required":["expiration_mode","infinite_token_lifetime","token_lifetime"],"properties":{"expiration_mode":{"enum":["expire","not-expire"]},"infinite_token_lifetime":{"type":"boolean"},"token_lifetime":{"type":"number"}}},"access_token":{"type":"object","properties":{"token_lifetime":{"type":"number","minimum":60,"description":"Access token lifetime in seconds for tokens issued to this client. A resolved audience (Api) token_lifetime takes precedence. Omit to use the tenant default (24h)."}}},"client_uri":{"type":"string","nullable":true},"logo_uri":{"type":"string","nullable":true},"tos_uri":{"type":"string","nullable":true},"policy_uri":{"type":"string","nullable":true},"initiate_login_uri":{"type":"string","description":"The URL in the application that starts a login. Where the hosted login screen sends people who open an expired or shared login link.","nullable":true},"contacts":{"type":"array","items":{"type":"string"}},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string"},"consent_required":{"type":"boolean","description":"When true, /authorize never issues a code in silence for this client: the End-User approves it on the consent screen first. Set on clients the tenant did not create (Client ID Metadata Documents, dynamic registration)."},"metadata_document_url":{"type":"string","description":"For a client identified by a Client ID Metadata Document: the URL (also its client_id) its metadata was fetched from."},"registered_dynamically":{"type":"boolean"},"application_type":{"type":"string"},"software_id":{"type":"string","nullable":true},"software_version":{"type":"string","nullable":true},"frontchannel_logout_uri":{"type":"string","nullable":true},"frontchannel_logout_session_required":{"type":"boolean"},"backchannel_logout_uri":{"type":"string","nullable":true},"backchannel_logout_session_required":{"type":"boolean"},"login_methods":{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"mfa_policy":{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},"recovery_channels":{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},"login_flow":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A login flow bound to this client (`loginflow_xxx`), overriding the account's. Absent = inherit."},"default_audience":{"type":"string","description":"Audience used when a client_credentials token request sends none (e.g. `faable:management:<account_id>`). An explicit `audience` in the request wins. Absent = `${iss}/userinfo`.","nullable":true},"management_scopes":{"type":"array","items":{"type":"string"},"description":"Management API scopes (e.g. `read:rolemembers`) this client may obtain with client_credentials for the audience `faable:management:<account_id>`. A token request gets the requested scopes that are in this list, or the whole list when it asks for none. Empty = the client cannot obtain Management API tokens."},"saml_sp":{"type":"object","required":["enabled","entity_id","acs_url"],"properties":{"enabled":{"type":"boolean","description":"Whether this client answers SAML requests. Off keeps the configuration but every /saml route of the client returns 404."},"entity_id":{"type":"string","minLength":1,"maxLength":1024,"description":"The Service Provider's Entity ID. Its AuthnRequests must carry it as Issuer, and every assertion is restricted to it as Audience."},"acs_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The Service Provider's Assertion Consumer Service URL (HTTP-POST binding), https. The only URL a response is ever posted to: an AuthnRequest naming another one is refused."},"nameid_format":{"enum":["emailAddress","persistent","unspecified"],"description":"`emailAddress` (default) sends the email as NameID; `persistent` sends the stable user id; `unspecified` sends the email when the user has one, the user id otherwise."},"attribute_mapping":{"type":"object","description":"SAML attribute name → user field. Omit for the default (email, name, given_name, family_name). Custom ID token claims set by post-login actions are added as attributes too.","additionalProperties":{"anyOf":[{"type":"string","enum":["user_id"]},{"type":"string","enum":["email"]},{"type":"string","enum":["email_verified"]},{"type":"string","enum":["name"]},{"type":"string","enum":["given_name"]},{"type":"string","enum":["family_name"]},{"type":"string","enum":["nickname"]},{"type":"string","enum":["picture"]},{"type":"string","enum":["phone_number"]}]}},"sign_response":{"type":"boolean","description":"Also sign the Response envelope, not only the Assertion. Default true; a few SPs reject a doubly-signed message."}},"additionalProperties":false,"nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ClientMetadata","default":{}},"createdAt":{"type":"string","description":"Client creation date"},"updatedAt":{"type":"string","description":"Client updated date"}},"description":"Client"},"ClientMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ClientMetadata","default":{}},"ClientCreate":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"callbacks":{"type":"array","items":{"type":"string"}},"logout_urls":{"type":"array","items":{"type":"string"}},"web_origins":{"type":"array","items":{"type":"string"}},"initiate_login_uri":{"type":"string"},"grant_types":{"type":"array","items":{"anyOf":[{"type":"string","enum":["authorization_code"]},{"type":"string","enum":["client_credentials"]},{"type":"string","enum":["refresh_token"]},{"type":"string","enum":["password"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/passwordless/otp"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:token-exchange"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:device_code"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-otp"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-recovery-code"]}],"description":"A grant the client may use at the token endpoint. An empty `grant_types` list allows every grant except `password`, which is always opt-in."},"uniqueItems":true},"management_scopes":{"type":"array","items":{"type":"string"},"uniqueItems":true,"description":"Management API scopes (e.g. `read:rolemembers`) this client may obtain with client_credentials for the audience `faable:management:<account_id>`. A token request gets the requested scopes that are in this list, or the whole list when it asks for none. Empty = the client cannot obtain Management API tokens. Only scopes your own token holds can be granted."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"ClientCreate","additionalProperties":false},"ClientGrantType":{"anyOf":[{"type":"string","enum":["authorization_code"]},{"type":"string","enum":["client_credentials"]},{"type":"string","enum":["refresh_token"]},{"type":"string","enum":["password"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/passwordless/otp"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:token-exchange"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:device_code"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-otp"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-recovery-code"]}],"description":"A grant the client may use at the token endpoint. An empty `grant_types` list allows every grant except `password`, which is always opt-in."},"ClientMetadataCreate":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"},"ClientUpdate":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable client name shown in consent screens."},"description":{"type":"string","description":"Free-form description for internal admin use."},"callbacks":{"type":"array","items":{"type":"string"},"description":"Whitelist of allowed OAuth `redirect_uri` values."},"logout_urls":{"type":"array","items":{"type":"string"},"description":"Whitelist of allowed `post_logout_redirect_uri` values."},"web_origins":{"type":"array","items":{"type":"string"},"description":"Whitelist of origins (scheme://host[:port], no path) allowed to make cross-origin (CORS) requests to the token and passwordless endpoints. Empty allows any origin."},"refresh_token":{"type":"object","required":["expiration_mode","infinite_token_lifetime","token_lifetime"],"properties":{"expiration_mode":{"enum":["expire","not-expire"]},"infinite_token_lifetime":{"type":"boolean"},"token_lifetime":{"type":"number"}}},"access_token":{"type":"object","properties":{"token_lifetime":{"type":"number","minimum":60,"description":"Access token lifetime in seconds for tokens issued to this client. A resolved audience (Api) token_lifetime takes precedence. Omit to use the tenant default (24h)."}}},"client_uri":{"type":"string","nullable":true},"logo_uri":{"type":"string","nullable":true},"tos_uri":{"type":"string","nullable":true},"policy_uri":{"type":"string","nullable":true},"initiate_login_uri":{"type":"string","description":"OIDC `initiate_login_uri`: the URL in your application that starts a login (it redirects to /authorize). When someone opens an expired or shared login link, the hosted login screen sends them here to start over. https only (http on localhost). `null` removes it.","nullable":true},"contacts":{"type":"array","items":{"type":"string"}},"grant_types":{"type":"array","items":{"anyOf":[{"type":"string","enum":["authorization_code"]},{"type":"string","enum":["client_credentials"]},{"type":"string","enum":["refresh_token"]},{"type":"string","enum":["password"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/passwordless/otp"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:token-exchange"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:device_code"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-otp"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-recovery-code"]}],"description":"A grant the client may use at the token endpoint. An empty `grant_types` list allows every grant except `password`, which is always opt-in."},"uniqueItems":true,"description":"Grants this client may use at the token endpoint. Empty allows every grant except `password`, which must be listed to be used."},"response_types":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string"},"application_type":{"type":"string"},"software_id":{"type":"string","nullable":true},"software_version":{"type":"string","nullable":true},"frontchannel_logout_uri":{"type":"string","nullable":true},"frontchannel_logout_session_required":{"type":"boolean"},"backchannel_logout_uri":{"type":"string","nullable":true},"backchannel_logout_session_required":{"type":"boolean"},"login_methods":{"anyOf":[{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},{"type":"null"}]},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"mfa_policy":{"anyOf":[{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},{"type":"null"}]},"recovery_channels":{"anyOf":[{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},{"type":"null"}]},"login_flow":{"anyOf":[{"type":"string"},{"type":"null"}]},"default_audience":{"type":"string","minLength":1,"description":"Audience a client_credentials token request gets when it sends none. An explicit `audience` wins. `null` removes it.","nullable":true},"management_scopes":{"type":"array","items":{"type":"string"},"uniqueItems":true,"description":"Management API scopes (e.g. `read:rolemembers`) this client may obtain with client_credentials for the audience `faable:management:<account_id>`. A token request gets the requested scopes that are in this list, or the whole list when it asks for none. Empty = the client cannot obtain Management API tokens. Replaces the whole list; `[]` removes all. Only scopes your own token holds can be added."},"saml_sp":{"anyOf":[{"type":"object","required":["enabled","entity_id","acs_url"],"properties":{"enabled":{"type":"boolean","description":"Whether this client answers SAML requests. Off keeps the configuration but every /saml route of the client returns 404."},"entity_id":{"type":"string","minLength":1,"maxLength":1024,"description":"The Service Provider's Entity ID. Its AuthnRequests must carry it as Issuer, and every assertion is restricted to it as Audience."},"acs_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The Service Provider's Assertion Consumer Service URL (HTTP-POST binding), https. The only URL a response is ever posted to: an AuthnRequest naming another one is refused."},"nameid_format":{"enum":["emailAddress","persistent","unspecified"],"description":"`emailAddress` (default) sends the email as NameID; `persistent` sends the stable user id; `unspecified` sends the email when the user has one, the user id otherwise."},"attribute_mapping":{"type":"object","description":"SAML attribute name → user field. Omit for the default (email, name, given_name, family_name). Custom ID token claims set by post-login actions are added as attributes too.","additionalProperties":{"anyOf":[{"type":"string","enum":["user_id"]},{"type":"string","enum":["email"]},{"type":"string","enum":["email_verified"]},{"type":"string","enum":["name"]},{"type":"string","enum":["given_name"]},{"type":"string","enum":["family_name"]},{"type":"string","enum":["nickname"]},{"type":"string","enum":["picture"]},{"type":"string","enum":["phone_number"]}]}},"sign_response":{"type":"boolean","description":"Also sign the Response envelope, not only the Assertion. Default true; a few SPs reject a doubly-signed message."}},"additionalProperties":false},{"type":"null"}]},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Free-form client metadata. Replaces the whole object — send the full merged value, not a partial delta. Omit it to leave the metadata untouched."}},"description":"Partial update for a Client. Only the supplied fields are modified. `client_id` and `client_secret` are not editable through this endpoint to prevent accidental rotation; use a dedicated endpoint when secret rotation is added.","additionalProperties":false},"ClientUpdateMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Free-form client metadata. Replaces the whole object — send the full merged value, not a partial delta. Omit it to leave the metadata untouched."},"User":{"type":"object","required":["id","email_verified","phone_verified","logins_count","user_metadata","app_metadata","account","createdAt"],"properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User name","nullable":true},"username":{"type":"string","description":"unique username for this user","nullable":true},"given_name":{"type":"string","description":"Given name","nullable":true},"family_name":{"type":"string","description":"Family name","nullable":true},"middle_name":{"type":"string","description":"Middle name (OIDC §5.1)","nullable":true},"nickname":{"type":"string","description":"Casual name. Distinct from given_name (e.g. \"Mike\" vs \"Michael\"). OIDC §5.1","nullable":true},"email":{"type":"string","description":"User email","nullable":true},"email_verified":{"type":"boolean","description":"true if email is verified","default":false},"email_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `email_verified` was last set. `manual` — admin flipped the flag via `POST /user/:id`. `verification_flow` — user clicked the verification link. `passwordless_otp` — user completed passwordless OTP login. `team_invite` — user clicked a team invitation link. `email_change` — user confirmed a self-service email change. `federated` — verified by the external IdP on OAuth callback. `password_reset` — user completed the recovery link that was emailed to them (the `email` channel only). `import` — the user arrived verified from another provider through `POST /user/import` (asserted by whoever ran the migration). `null` — the field was cleared (admin un-verified the email)."},"email_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `email_verified` was last flipped to true.","nullable":true},"email_change_locked_at":{"type":"string","description":"ISO 8601 timestamp of the user's last verified email change. When set, OAuth callbacks will not overwrite `email`/`email_verified` from the federated provider — the manually-chosen email wins.","nullable":true},"email_bounced_at":{"type":"string","description":"ISO 8601 timestamp of the last Postmark hard bounce / spam complaint for this address. When set, the email is treated as undeliverable and no further emails are sent to the user until the address changes.","nullable":true},"welcome_sent_at":{"type":"string","description":"ISO 8601 timestamp of the built-in welcome email send. Set once by the platform; guarantees at most one welcome per user.","nullable":true},"imported_at":{"type":"string","description":"ISO 8601 timestamp of when the user was created by `POST /user/import`. Imported users get none of the built-in new-user emails (verification, welcome, `user.created` notifications); `user.created` webhooks still fire and carry this field.","nullable":true},"passkey_prompted_at":{"type":"string","description":"ISO 8601 timestamp of the last time the hosted \"create a passkey\" offer was shown to this user after a login. Drives the snooze in `login_methods.passkey_promotion_snooze_days`.","nullable":true},"passkey_prompt_count":{"type":"integer","description":"How many times the passkey offer has been shown to this user. Capped by `login_methods.passkey_promotion_max_prompts`; reset by an administrator through `POST /user/:id/passkey-prompt/reset`."},"passkey_prompt_dismissed_at":{"type":"string","description":"ISO 8601 timestamp of the last time the user chose \"Not now\" on the passkey offer, as opposed to closing the tab.","nullable":true},"phone":{"type":"string","description":"contact phone number","nullable":true},"phone_e164":{"type":"boolean","description":"Whether `phone` is a valid E.164 number. Calculated on read, never stored: new writes are rejected unless they normalise, so a `false` here is a legacy row that no SMS can reach — fix the number, or set the account default country and write it again."},"phone_verified":{"type":"boolean","description":"phone is verified"},"phone_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `phone_verified` was last set. Same enum as `email_verified_method`, plus `sms_otp` — the user typed a code sent by SMS (`POST /user/:id/verify-phone/start`)."},"phone_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `phone_verified` was last flipped to true.","nullable":true},"country_iso":{"type":"string","description":"country iso code","nullable":true},"birth_date":{"type":"string","description":"user birth_date","nullable":true},"gender":{"type":"string","description":"User gender (free-form, OIDC §5.1)","nullable":true},"zoneinfo":{"type":"string","description":"IANA time-zone name (e.g. \"Europe/Madrid\"). OIDC §5.1 — used for the `zoneinfo` claim.","nullable":true},"locale":{"type":"string","description":"user main language","nullable":true},"region":{"type":"string","description":"customer region","nullable":true},"website":{"type":"string","description":"URL of the User's personal Web page or blog (OIDC §5.1)","nullable":true},"address":{"type":"object","properties":{"formatted":{"type":"string","description":"Full mailing address, formatted for display (may contain newlines)","nullable":true},"street_address":{"type":"string","nullable":true},"locality":{"type":"string","description":"City or locality","nullable":true},"region":{"type":"string","description":"State, province, or region","nullable":true},"postal_code":{"type":"string","nullable":true},"country":{"type":"string","description":"Country name","nullable":true}},"additionalProperties":false,"nullable":true},"picture":{"type":"string","description":"User picture url","nullable":true},"logins_count":{"type":"number","description":"Total number of logins this user has performed."},"last_ip":{"type":"string","description":"Last IP address from which this user logged in","nullable":true},"last_login":{"type":"string","description":"Last date and time this user logged in (ISO_8601 format)","nullable":true},"first_language":{"type":"string","description":"Primary Accept-Language subtag seen at this user's FIRST login (e.g. \"zh\"). Written once and never updated, so a later login whose language differs is visible as `event.stats.language_changed` in actions — a cross-session signal no single request can produce.","nullable":true},"internal_metadata":{"type":"object","properties":{"last_ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}},"signup":{"type":"object","required":["at"],"properties":{"ip":{"type":"string"},"user_agent":{"type":"string"},"language":{"type":"string"},"at":{"type":"string","format":"date-time"},"ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}}}}},"readOnly":true,"description":"Computed by Faable Auth, never writable through the API. `last_ip_info`: country, network (ASN) and reputation of `last_ip`, resolved after each login. `signup`: the IP, browser and language of the request that created the user, so a user who signed up but never logged in is not a blank — with the same resolution in `ip_info`. IP geolocation by DB-IP (CC BY 4.0)."},"suspended":{"type":"boolean","description":"true while the user is suspended. A suspended user is rejected at every login flow, token grant, session use and management-API call.","default":false},"suspended_at":{"type":"string","description":"ISO 8601 timestamp of when `suspended` was last flipped to true.","nullable":true},"suspended_reason":{"type":"string","description":"Free-form reason recorded when the user was suspended (e.g. \"abuse: RCE payload\").","nullable":true},"user_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"User Metadata","default":{}},"app_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"App Metadata","default":{}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"UserMetadata","default":{}},"createdAt":{"type":"string","description":"User creation date"},"updatedAt":{"type":"string","description":"User updated date"}},"description":"User"},"UserAddress":{"type":"object","properties":{"formatted":{"type":"string","description":"Full mailing address, formatted for display (may contain newlines)","nullable":true},"street_address":{"type":"string","nullable":true},"locality":{"type":"string","description":"City or locality","nullable":true},"region":{"type":"string","description":"State, province, or region","nullable":true},"postal_code":{"type":"string","nullable":true},"country":{"type":"string","description":"Country name","nullable":true}},"additionalProperties":false,"nullable":true},"UserUserMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"User Metadata","default":{}},"UserAppMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"App Metadata","default":{}},"UserMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"UserMetadata","default":{}},"UserCreate":{"type":"object","properties":{"name":{"type":"string","description":"The user's full name."},"given_name":{"type":"string","description":"The user's given name."},"family_name":{"type":"string","description":"The user's family name."},"email":{"type":"string","description":"The user's email."},"phone":{"type":"string","description":"contact phone number"},"birth_date":{"type":"string","description":"The user's birth date"},"locale":{"type":"string","description":"The user's default locale lang."},"picture":{"type":"string","description":"A URI pointing to the user's picture."},"user_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"User Metadata"},"app_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"App Metadata"}},"description":"UserCreate","additionalProperties":false},"UserUserCreate":{"type":"object","properties":{},"additionalProperties":true,"description":"User Metadata"},"UserAppCreate":{"type":"object","properties":{},"additionalProperties":true,"description":"App Metadata"},"UserUpdate":{"type":"object","properties":{"name":{"type":"string","description":"name"},"given_name":{"type":"string","description":"given name"},"family_name":{"type":"string","description":"family name"},"email":{"type":"string","description":"Contact email. Changing the email to a new value automatically resets `email_verified` to false (and clears `email_verified_method` / `email_verified_at`) unless the same patch sets `email_verified` explicitly. The override is the documented way to import a pre-verified user from another IdP."},"email_verified":{"type":"boolean","description":"Flip the email verification flag. Setting `true` marks the email as verified by the admin (`email_verified_method=manual`). Setting `false` clears the verification metadata."},"phone":{"type":"string","description":"Contact phone number. Changing it to a new value automatically resets `phone_verified` to false (and clears `phone_verified_method` / `phone_verified_at`) unless the same patch sets `phone_verified` explicitly."},"phone_verified":{"type":"boolean","description":"Flip the phone verification flag. Setting `true` marks the phone as verified by the admin (`phone_verified_method=manual`)."},"suspended":{"type":"boolean","description":"Suspend (`true`) or reinstate (`false`) the user. While suspended, every login flow, token grant, session use and management-API call is rejected. Setting `true` auto-stamps `suspended_at`; setting `false` clears `suspended_at` / `suspended_reason`."},"suspended_reason":{"type":"string","maxLength":512,"description":"Free-form reason for the suspension (e.g. \"abuse: RCE payload\"). Stored verbatim for audit purposes."},"country_iso":{"type":"string","description":"user country as iso string"},"birth_date":{"type":"string","description":"user birth_date"},"locale":{"type":"string","description":"user main language","nullable":true},"picture":{"type":"string","description":"User picture url"},"user_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"User defined metadata"},"app_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"App defined metadata"}},"description":"UserUpdate","additionalProperties":false},"UserUserMetadataUpdate":{"type":"object","properties":{},"additionalProperties":true,"description":"User defined metadata"},"UserAppMetadataUpdate":{"type":"object","properties":{},"additionalProperties":true,"description":"App defined metadata"},"AdminFactor":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string"},"type":{"type":"string"},"name":{"type":"string"},"confirmed_at":{"type":"string"},"last_used_at":{"type":"string"},"remaining":{"type":"integer"}}},"UserIpInsight":{"type":"object","properties":{"ip":{"type":"string"},"source":{"anyOf":[{"type":"string","enum":["last_login"]},{"type":"string","enum":["signup"]}],"description":"`last_login`: the IP of the latest login. `signup`: the user never logged in, so this is the IP of the request that created them."},"geo":{"type":"object","required":["source"],"properties":{"country_code":{"type":"string"},"country_name":{"type":"string"},"continent":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"source":{"type":"object","required":["name","url","license","edition"],"properties":{"name":{"type":"string"},"url":{"type":"string"},"license":{"type":"string"},"edition":{"type":"string"}},"description":"Where the location comes from. The data is CC BY 4.0: anything that shows it must credit this source."}}},"signals":{"type":"object","required":["active_blocks","network_active_blocks","suspended_denies","network_suspended_denies"],"properties":{"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"active_blocks":{"type":"integer"},"network_active_blocks":{"type":"integer"},"suspended_denies":{"type":"integer"},"network_suspended_denies":{"type":"integer"}},"description":"The same values a tenant Action reads as `event.request.ip_*` for a login from this IP now. `datacenter` and `apple_private_relay` are absent when the IP service cannot answer."},"activity":{"type":"object","required":["logins","active_sessions","other_users"],"properties":{"logins":{"type":"integer","description":"This user's logins from this IP in the last year."},"first_login_at":{"type":"string","format":"date-time"},"active_sessions":{"type":"integer","description":"This user's active sessions last seen from this IP."},"other_users":{"type":"integer","description":"Other users of this tenant whose last login came from this IP."}}}}},"Identity":{"type":"object","required":["id","connection","user","identity_id","profile_data","account","createdAt"],"properties":{"id":{"type":"string","description":"Identity ID"},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"identity_id":{"type":"string"},"access_token":{"type":"string"},"access_token_secret":{"type":"string"},"refresh_token":{"type":"string"},"access_token_expires_at":{"type":"string","description":"ISO 8601 timestamp when the stored provider `access_token` expires. Stamped from the provider `expires_in` on (re-)authorization and refresh. Absent when the provider issues non-expiring tokens."},"profile_data":{},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"IdentityMetadata","default":{}},"createdAt":{"type":"string","description":"Identity creation date"},"updatedAt":{"type":"string","description":"Identity updated date"}},"description":"Identity"},"IdentityMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"IdentityMetadata","default":{}},"IdentityCreate":{"type":"object","required":["user","identity_id","connection","profile_data"],"properties":{"user":{"type":"string"},"identity_id":{"type":"string"},"connection":{"type":"string"},"access_token":{"type":"string"},"access_token_secret":{"type":"string"},"refresh_token":{"type":"string"},"profile_data":{}}},"Credential":{"type":"object","required":["id","connection","user","account","createdAt"],"properties":{"id":{"type":"string","description":"Credential ID"},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"email":{"type":"string","description":"Email login identifier (unique within the connection)."},"username":{"type":"string","description":"Username login identifier (unique within the connection)."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"CredentialMetadata","default":{}},"createdAt":{"type":"string","description":"Credential creation date"},"updatedAt":{"type":"string","description":"Credential updated date"}},"description":"Credential"},"CredentialMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"CredentialMetadata","default":{}},"CredentialsCreate":{"type":"object","required":["user","connection"],"properties":{"user":{"type":"string"},"connection":{"type":"string"},"email":{"type":"string"},"username":{"type":"string"}},"additionalProperties":false},"CredentialsUpdate":{"type":"object","properties":{"email":{"type":"string"},"username":{"type":"string"}},"additionalProperties":false},"Role":{"type":"object","required":["id","name","description","permissions","account","createdAt"],"properties":{"id":{"type":"string","description":"Role ID"},"name":{"type":"string","description":"Short, human-readable identifier for the role (e.g. `admin`, `editor`, `billing-manager`). Surfaced in the dashboard role pickers.","example":"admin"},"description":{"type":"string","description":"Free-form explanation of what users in this role are expected to do or have access to. Shown alongside the role in the dashboard."},"permissions":{"type":"array","items":{"type":"object","required":["api","value"],"properties":{"api":{"type":"string","description":"Id of the Api that defines the permission.","example":"api_6555fd293acc2f0fac0e3452"},"value":{"type":"string","description":"The permission, as it appears in the Api's `permissions` catalog.","example":"invites:write"}},"additionalProperties":false,"description":"A permission of one Api granted by a role."},"description":"Api permissions granted to the users that hold this role at the account level (team roles grant none). Change them with `POST` / `DELETE /role/:role_id/permissions`; `GET /role/:role_id/permissions` lists them with their Api."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"RoleMetadata","default":{}},"createdAt":{"type":"string","description":"Role creation date"},"updatedAt":{"type":"string","description":"Role updated date"}},"description":"Role"},"RoleMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"RoleMetadata","default":{}},"RoleCreate":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Short, human-readable identifier for the role (e.g. `admin`, `editor`).","example":"admin"},"description":{"type":"string","description":"Free-form explanation of what users in this role are expected to do or have access to."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"RoleCreate","additionalProperties":false},"RoleMetadataCreate":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"},"RoleUpdate":{"type":"object","properties":{"name":{"type":"string","description":"New human-readable identifier for the role."},"description":{"type":"string","description":"New free-form description of what users in this role do."}},"description":"Partial update for a Role. Only the supplied fields are modified.","additionalProperties":false},"RoleMember":{"type":"object","required":["id","role","user","account","createdAt"],"properties":{"id":{"type":"string","description":"RoleMember ID"},"role":{"anyOf":[{"$ref":"#/components/schemas/Role"},{"type":"string"},{}],"description":"Role granted to the user. Returned as an id by default; pass `?expand=role` to inline the full Role object."},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}],"description":"User the role is granted to. Returned as an id by default; pass `?expand=user` to inline the full User object."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"RoleMemberMetadata","default":{}},"createdAt":{"type":"string","description":"RoleMember creation date"},"updatedAt":{"type":"string","description":"RoleMember updated date"}},"description":"RoleMember"},"RoleMemberMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"RoleMemberMetadata","default":{}},"Team":{"type":"object","required":["id","name","slug","description","logo_url","account","createdAt"],"properties":{"id":{"type":"string","description":"Team ID"},"name":{"type":"string","description":"Human-readable name of the team. Shown in the dashboard and in team-picker UI.","example":"Acme Engineering"},"slug":{"type":"string","description":"URL-safe identifier auto-derived from `name`. Unique within the tenant; used in routes that address a team by slug.","example":"acme-engineering"},"description":{"type":"string","description":"Optional free-form description of the team.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of an image used as the team avatar/logo.","nullable":true},"organization":{"type":"string","description":"Id of the Organization the team belongs to, or `null`. Changed only with `PUT /team/{team_id}/organization`.","nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMetadata","default":{}},"createdAt":{"type":"string","description":"Team creation date"},"updatedAt":{"type":"string","description":"Team updated date"}},"description":"Team"},"TeamMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMetadata","default":{}},"TeamCreate":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Human-readable name of the team.","example":"Acme Engineering"},"description":{"type":"string","description":"Optional free-form description of the team."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"TeamCreate","additionalProperties":false},"TeamMetadataCreate":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"},"TeamUpdate":{"type":"object","properties":{"name":{"type":"string","description":"New human-readable name for the team."},"description":{"type":"string","description":"New free-form description for the team."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Arbitrary key/value pairs attached to the team. Replaces the previous metadata entirely."}},"description":"Partial update for a Team. Only the supplied fields are modified. `slug` is auto-derived from `name` and is not user-editable.","additionalProperties":false},"TeamUpdateMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Arbitrary key/value pairs attached to the team. Replaces the previous metadata entirely."},"Organization":{"type":"object","required":["id","name","slug","description","logo_url","domains","require_sso","auto_join","admins","account","createdAt"],"properties":{"id":{"type":"string","description":"Organization ID"},"name":{"type":"string","description":"Human-readable name of the organization.","example":"Acme Inc."},"slug":{"type":"string","description":"URL-safe identifier derived from `name`. Unique within the tenant.","example":"acme-inc"},"description":{"type":"string","description":"Optional free-form description.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of the organization logo.","nullable":true},"domains":{"type":"array","items":{"type":"object","required":["domain","verification_token","verified_at"],"properties":{"domain":{"type":"string","example":"acme.com"},"verification_token":{"type":"string","description":"Put `faable-verification=<verification_token>` in a TXT record at `_faable-challenge.<domain>`, then call `POST /organization/{organization_id}/domain/{domain}/verify`."},"verified_at":{"type":"string","format":"date-time","description":"When the TXT record was found. `null` = not verified.","nullable":true}}},"description":"Email domains the organization claims. Only a verified one counts: it is how the organization's people are recognized."},"require_sso":{"type":"boolean","description":"People of the verified domains reach the organization's teams only through its identity provider. Applied by the resource server with the `https://faable.com/org` and `https://faable.com/org_sso` access-token claims."},"auto_join":{"type":"array","items":{"type":"object","required":["team","roles"],"properties":{"team":{"type":"string","description":"A team of this organization."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids the member gets in that team."}}},"description":"Whoever signs in with a verified email of one of the verified domains joins these teams. Empty = nobody joins by domain."},"admins":{"type":"array","items":{"type":"string"},"description":"Ids of the users who manage the organization: its teams and its identity settings. Being an admin does not grant access to any team; team access is team membership."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"OrganizationMetadata","default":{}},"createdAt":{"type":"string","description":"Organization creation date"},"updatedAt":{"type":"string","description":"Organization updated date"}},"description":"Organization"},"OrganizationMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"OrganizationMetadata","default":{}},"OrganizationCreate":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"description":"Human-readable name of the organization.","example":"Acme Inc."},"description":{"type":"string","description":"Optional free-form description."},"logo_url":{"type":"string","format":"uri","description":"Optional URL of the organization logo."},"admins":{"type":"array","items":{"type":"string","pattern":"^user_[0-9a-f]{24}$"},"uniqueItems":true,"description":"Initial admins (user ids of this tenant). Can be empty and filled later with `POST /organization/{organization_id}/admin`."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"OrganizationCreate","additionalProperties":false},"OrganizationMetadataCreate":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"},"OrganizationUpdate":{"type":"object","properties":{"name":{"type":"string","minLength":1,"description":"New human-readable name."},"description":{"type":"string","description":"New free-form description."},"logo_url":{"type":"string","format":"uri","description":"New URL of the organization logo."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Arbitrary key/value pairs. Replaces the previous metadata entirely."}},"description":"Partial update for an Organization. `slug` is derived from `name` on creation and is not editable; admins change through `/organization/{organization_id}/admin`.","additionalProperties":false},"OrganizationUpdateMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Arbitrary key/value pairs. Replaces the previous metadata entirely."},"TeamMember":{"type":"object","required":["id","user","team","roles","account","createdAt"],"properties":{"id":{"type":"string","description":"TeamMember ID"},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}],"description":"User that belongs to the team. Returned as an id by default; pass `?expand=user` to inline the full User object."},"team":{"anyOf":[{"$ref":"#/components/schemas/Team"},{"type":"string"},{}],"description":"Team the user belongs to. Returned as an id by default; pass `?expand=team` to inline the full Team object."},"roles":{"type":"array","items":{"anyOf":[{"$ref":"#/components/schemas/Role"},{"type":"string"},{}]},"description":"Roles granted to the user *within this team*. Distinct from account-wide RoleMember grants — these only apply in the scope of the team. Each entry is an id by default; pass `?expand=roles` to inline."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMemberMetadata","default":{}},"createdAt":{"type":"string","description":"TeamMember creation date"},"updatedAt":{"type":"string","description":"TeamMember updated date"}},"description":"TeamMember"},"TeamMemberMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMemberMetadata","default":{}},"Permission":{"type":"object","required":["value"],"properties":{"value":{"type":"string","description":"Permission identifier as it appears in the `permissions` claim of an access_token (e.g. `read:users`). Use `<verb>:<resource>` by convention.","example":"read:users"},"description":{"type":"string","description":"Human-readable explanation shown in the consent prompt."}},"description":"A permission exposed by an API: a single value that may end up in the `permissions` claim of an access_token, plus a human-readable description shown in the consent prompt."},"PermissionCreate":{"type":"object","required":["value"],"properties":{"value":{"type":"string","description":"Permission identifier as it will appear in the `permissions` claim (e.g. `read:users`). Convention: `<verb>:<resource>`.","example":"read:users"},"description":{"type":"string","description":"Human-readable explanation shown in the consent prompt."}},"additionalProperties":false,"description":"Payload for declaring a single permission inside an Api."},"ApiSigningAlg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign access_tokens issued for this API. Currently only RS256 (the account keystore default) is implemented."},"ApiTokenDialect":{"anyOf":[{"type":"string","enum":["access_token"]},{"type":"string","enum":["access_token_authz"]}],"description":"Shape of access_tokens issued for this API. `access_token` emits the standard OAuth2 token. `access_token_authz` additionally embeds a `permissions` claim listing the permissions granted to the subject for this API."},"Api":{"type":"object","required":["id","name","slug","identifier","permissions","signing_alg","token_dialect","token_lifetime","enforce_policies","rbac","allow_offline_access","skip_consent","include_teams_in_access_token","include_roles_in_access_token","include_teams_in_id_token","include_roles_in_id_token","account","createdAt"],"properties":{"id":{"type":"string","description":"Api ID"},"name":{"type":"string","description":"Human-readable name for the API. Shown in the dashboard and consent prompts.","example":"My Backend API"},"slug":{"type":"string","description":"URL-safe identifier auto-derived from `name`. Unique within the tenant."},"identifier":{"type":"string","description":"Audience URL that clients pass as `audience=` when requesting a token for this API. Becomes the `aud` claim in the issued access_token. Immutable after creation and unique within the tenant.","example":"https://api.faable.com"},"description":{"type":"string","description":"Optional human-readable description of the API."},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"},"description":"Set of permissions (scopes) this API exposes. When a token is issued with `audience=identifier`, the requested `scope` is intersected with this list to populate the `permissions` claim."},"signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign access_tokens issued for this API. Currently only RS256 (the account keystore default) is implemented."},"token_dialect":{"anyOf":[{"type":"string","enum":["access_token"]},{"type":"string","enum":["access_token_authz"]}],"description":"Shape of access_tokens issued for this API. `access_token` emits the standard OAuth2 token. `access_token_authz` additionally embeds a `permissions` claim listing the permissions granted to the subject for this API."},"token_lifetime":{"type":"integer","description":"Access_token lifetime in seconds. Default 86400 (24h). Applied at token issuance time (overrides the generic default).","minimum":60,"maximum":2592000},"enforce_policies":{"type":"boolean","description":"When true, only scopes that match a permission in `permissions` are forwarded into the access_token. When false, requested scopes are echoed back verbatim with no filtering."},"rbac":{"type":"boolean","description":"Role-based access control. When true, a token issued to a user carries only the permissions of this API granted to the user's roles (account-level role assignments, not team roles): `permissions` lists all of them, and `scope` keeps only the requested ones the user holds. Machine-to-machine tokens (`client_credentials`) are not affected. The `permissions` claim requires `token_dialect: access_token_authz`."},"allow_offline_access":{"type":"boolean","description":"When true, the API is eligible to be the target of a `refresh_token` (i.e. clients can request `offline_access` against it)."},"consent_options":{"type":"object","required":["claim","label","url","audience"],"properties":{"claim":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,31}$","description":"Access token claim that carries the chosen id."},"label":{"type":"string","maxLength":60,"description":"What the screen asks for, e.g. \"Project\"."},"url":{"type":"string","pattern":"^https://","description":"GET endpoint of the resource server that lists the choices."},"audience":{"type":"string","description":"Identifier of a registered API the listing token is issued for (usually the resource server behind `url`)."},"scope":{"type":"string","description":"Space-separated permissions of the listing token. The token is delegated (`act`): the resource server sees auth acting for the user with only these, never the user’s full powers — an admin is listed their own choices, not the whole platform’s."}},"nullable":true},"delegation":{"type":"object","required":["client_id","audience"],"properties":{"client_id":{"type":"string","description":"The confidential client of the resource server allowed to exchange."},"audience":{"type":"string","description":"Identifier of the registered API the new token is for."}},"nullable":true},"skip_consent":{"type":"boolean","description":"When true, the consent prompt is skipped for first-party clients (clients owned by the same tenant as the API)."},"include_teams_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `teams` claim listing the slugs of the teams the subject is a member of within the tenant."},"include_roles_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `roles` claim listing the names of the roles the subject holds across their team memberships within the tenant."},"include_teams_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `teams` claim with the subject team slugs. The id_token only sees the flag when the client requested `audience=` so this API is resolved at issuance time."},"include_roles_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `roles` claim with the subject role names. Same audience caveat as `include_teams_in_id_token`."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ApiMetadata","default":{}},"createdAt":{"type":"string","description":"Api creation date"},"updatedAt":{"type":"string","description":"Api updated date"}},"description":"Api"},"ApiMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ApiMetadata","default":{}},"ApiCreate":{"type":"object","required":["name","identifier"],"properties":{"name":{"type":"string","description":"Human-readable name for the API.","example":"My Backend API"},"identifier":{"type":"string","description":"Audience URL clients pass as `audience=` when requesting a token for this API. Must be unique within the tenant and is immutable after creation.","example":"https://api.faable.com"},"description":{"type":"string","description":"Optional human-readable description of the API."},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/PermissionCreate"},"description":"Set of permissions (scopes) this API exposes. Tokens issued with `audience=identifier` will only carry these in their `permissions` claim.","default":[]},"signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign access_tokens issued for this API. Currently only RS256 (the account keystore default) is implemented."},"token_dialect":{"anyOf":[{"type":"string","enum":["access_token"]},{"type":"string","enum":["access_token_authz"]}],"description":"Shape of access_tokens issued for this API. `access_token` emits the standard OAuth2 token. `access_token_authz` additionally embeds a `permissions` claim listing the permissions granted to the subject for this API."},"token_lifetime":{"type":"integer","description":"Access_token lifetime in seconds. Defaults to 86400 (24h) if omitted.","minimum":60,"maximum":2592000},"enforce_policies":{"type":"boolean","description":"When true, requested scopes are filtered against `permissions` at token issuance."},"rbac":{"type":"boolean","description":"When true, user tokens carry only the permissions granted to the user's roles. Defaults to false."},"allow_offline_access":{"type":"boolean","description":"When true, clients can request `offline_access` and receive a refresh_token for this API."},"consent_options":{"type":"object","required":["claim","label","url","audience"],"properties":{"claim":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,31}$","description":"Access token claim that carries the chosen id."},"label":{"type":"string","maxLength":60,"description":"What the screen asks for, e.g. \"Project\"."},"url":{"type":"string","pattern":"^https://","description":"GET endpoint of the resource server that lists the choices."},"audience":{"type":"string","description":"Identifier of a registered API the listing token is issued for (usually the resource server behind `url`)."},"scope":{"type":"string","description":"Space-separated permissions of the listing token. The token is delegated (`act`): the resource server sees auth acting for the user with only these, never the user’s full powers — an admin is listed their own choices, not the whole platform’s."}}},"delegation":{"type":"object","required":["client_id","audience"],"properties":{"client_id":{"type":"string","description":"The confidential client of the resource server allowed to exchange."},"audience":{"type":"string","description":"Identifier of the registered API the new token is for."}}},"skip_consent":{"type":"boolean","description":"When true, skip the consent prompt for first-party clients."},"include_teams_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `teams` claim with the subject team slugs."},"include_roles_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `roles` claim with the subject role names."},"include_teams_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `teams` claim with the subject team slugs."},"include_roles_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `roles` claim with the subject role names."}},"additionalProperties":false,"description":"Payload for creating an Api (a.k.a. Resource Server / Audience). The `identifier` becomes the `aud` claim in tokens issued for this API and cannot be changed after creation."},"ApiUpdate":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable name for the API."},"description":{"type":"string","description":"Optional human-readable description of the API."},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/PermissionCreate"},"description":"Replaces the full permissions list. Tokens issued AFTER the update will use the new list; tokens already in circulation are unchanged."},"signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign access_tokens issued for this API. Currently only RS256 (the account keystore default) is implemented."},"token_dialect":{"anyOf":[{"type":"string","enum":["access_token"]},{"type":"string","enum":["access_token_authz"]}],"description":"Shape of access_tokens issued for this API. `access_token` emits the standard OAuth2 token. `access_token_authz` additionally embeds a `permissions` claim listing the permissions granted to the subject for this API."},"token_lifetime":{"type":"integer","description":"Access_token lifetime in seconds.","minimum":60,"maximum":2592000},"enforce_policies":{"type":"boolean","description":"When true, requested scopes are filtered against `permissions` at token issuance."},"rbac":{"type":"boolean","description":"When true, user tokens carry only the permissions granted to the user's roles. Applies to tokens issued after the update, including refreshes."},"allow_offline_access":{"type":"boolean","description":"When true, clients can request `offline_access` and receive a refresh_token for this API."},"consent_options":{"type":"object","required":["claim","label","url","audience"],"properties":{"claim":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,31}$","description":"Access token claim that carries the chosen id."},"label":{"type":"string","maxLength":60,"description":"What the screen asks for, e.g. \"Project\"."},"url":{"type":"string","pattern":"^https://","description":"GET endpoint of the resource server that lists the choices."},"audience":{"type":"string","description":"Identifier of a registered API the listing token is issued for (usually the resource server behind `url`)."},"scope":{"type":"string","description":"Space-separated permissions of the listing token. The token is delegated (`act`): the resource server sees auth acting for the user with only these, never the user’s full powers — an admin is listed their own choices, not the whole platform’s."}},"nullable":true},"delegation":{"type":"object","required":["client_id","audience"],"properties":{"client_id":{"type":"string","description":"The confidential client of the resource server allowed to exchange."},"audience":{"type":"string","description":"Identifier of the registered API the new token is for."}},"nullable":true},"skip_consent":{"type":"boolean","description":"When true, skip the consent prompt for first-party clients."},"include_teams_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `teams` claim with the subject team slugs."},"include_roles_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `roles` claim with the subject role names."},"include_teams_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `teams` claim with the subject team slugs."},"include_roles_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `roles` claim with the subject role names."}},"additionalProperties":false,"description":"Partial update for an Api. `identifier` is immutable and not present here; recreate the API if you need to change it."},"Action":{"type":"object","required":["id","name","triggers","code","enabled","account","createdAt"],"properties":{"id":{"type":"string","description":"Action ID"},"name":{"type":"string","description":"Action Name","maxLength":200},"triggers":{"type":"array","items":{"enum":["continue","post-login","client-credentials"]},"description":"Triggers this action runs on. Derived from the hooks the code exports (`onExecutePostLogin` → post-login, `onExecuteContinue` → continue, `onExecuteClientCredentials` → client-credentials); read-only."},"code":{"type":"string","description":"JavaScript Code to execute"},"enabled":{"type":"boolean","description":"Is this action active?","default":true},"order":{"type":"number","description":"Execution order: lower runs first; on ties the newest action runs first","default":0},"revision":{"type":"number","description":"Monotonic edit counter — increments on every update. Audit rows stamp the revision that produced each allow/deny (`data.action_revision`); `updatedAt` dates the current revision.","default":1},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ActionMetadata","default":{}},"createdAt":{"type":"string","description":"Action creation date"},"updatedAt":{"type":"string","description":"Action updated date"}},"description":"Action"},"ActionMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ActionMetadata","default":{}},"ActionCreate":{"type":"object","required":["name","code"],"properties":{"name":{"type":"string","description":"Action Name","maxLength":200},"code":{"type":"string","description":"JavaScript code. Must export at least one hook: exports.onExecutePostLogin, exports.onExecuteContinue and/or exports.onExecuteClientCredentials. The triggers are derived from the exported hooks."},"enabled":{"type":"boolean","description":"Is this action active?","default":true},"order":{"type":"number","description":"Execution order: lower runs first; on ties the newest action runs first","default":0}}},"ActionUpdate":{"type":"object","properties":{"name":{"type":"string","description":"Action Name","maxLength":200},"code":{"type":"string","description":"JavaScript code. The triggers are re-derived from the hooks it exports."},"enabled":{"type":"boolean","description":"Is this action active?"},"order":{"type":"number","description":"Execution order"}},"description":"Partial update for an Action. Only the supplied fields are modified; `triggers` is read-only and follows `code`.","additionalProperties":false},"LoginFlow":{"type":"object","required":["id","name","trigger","graph","revision","history","account","createdAt"],"properties":{"id":{"type":"string","description":"LoginFlow ID"},"name":{"type":"string","maxLength":200},"trigger":{"type":"string","enum":["login"]},"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer","description":"Monotonic edit counter, bumped on every update. A publish freezes the graph under the revision it had; a login in flight pins the revision it started on."},"published":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"history":{"type":"array","items":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"description":"Previously published graphs, newest first, for rollback."},"layout":{"type":"object","description":"Node positions in the editor. Presentation only.","additionalProperties":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}},"additionalProperties":false}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"LoginFlowMetadata","default":{}},"createdAt":{"type":"string","description":"LoginFlow creation date"},"updatedAt":{"type":"string","description":"LoginFlow updated date"}},"description":"LoginFlow"},"LoginFlowMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"LoginFlowMetadata","default":{}},"LoginFlowCreate":{"type":"object","required":["name","graph"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"layout":{"type":"object","description":"Node positions in the editor. Presentation only.","additionalProperties":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}},"additionalProperties":false}}},"additionalProperties":false},"LoginFlowUpdate":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"layout":{"type":"object","description":"Node positions in the editor. Presentation only.","additionalProperties":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}},"additionalProperties":false}}},"additionalProperties":false},"NotificationSubscription":{"anyOf":[{"type":"object","required":["id","events","channel","active","config","account","createdAt"],"properties":{"id":{"type":"string","description":"NotificationSubscriptionEmail ID"},"events":{"type":"array","items":{"type":"string","description":"Events to subscribe to (e.g., user.created)"}},"channel":{"type":"string","enum":["email"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["recipients"],"properties":{"recipients":{"type":"array","items":{"type":"string","description":"List of email addresses receiving notifications","format":"email"}}},"additionalProperties":false},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionEmailMetadata","default":{}},"createdAt":{"type":"string","description":"NotificationSubscriptionEmail creation date"},"updatedAt":{"type":"string","description":"NotificationSubscriptionEmail updated date"}},"description":"NotificationSubscriptionEmail"},{"type":"object","required":["id","events","channel","active","config","account","createdAt"],"properties":{"id":{"type":"string","description":"NotificationSubscriptionWebhook ID"},"events":{"type":"array","items":{"type":"string"},"description":"Events to subscribe to. For `webhook` subscriptions it must be a subset of the published catalog; any other value is rejected with a 400:\n\n- `user.created`: A user was created (signup, import or Management API).\n- `user.updated`: A user was updated.\n- `user.deleted`: A user was deleted.\n- `auth.login`: A user logged in.\n- `credentials.exported`: Someone exported the tenant's password hashes (`GET /user/export` with hashes).\n- `team.created`: A team was created.\n- `team.updated`: A team was updated.\n- `team.deleted`: A team was deleted.\n- `team.member.added`: A user was added to a team.\n- `team.member.updated`: A team member's roles changed.\n- `team.member.removed`: A user was removed from a team (or left it).\n- `team.invite.created`: An invitation to join a team was created.\n- `team.invite.revoked`: A pending team invitation was revoked.\n- `team.invite.accepted`: A team invitation was accepted.\n- `organization.created`: An organization was created.\n- `organization.updated`: An organization was updated (name, settings or admins).\n- `organization.deleted`: An organization was deleted. Its teams are no longer in any organization; no `team.updated` is sent for them.\n\n`team.*` and `organization.*` payloads are notices, not state: they carry ids (`team_id`, `organization_id`, `user_id`, `roles`, `invite_id`...) and the receiver reads whatever else it needs. Use the delivery id (`x-faable-delivery`) to deduplicate: retries keep it."},"channel":{"type":"string","enum":["webhook"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Webhook URL","format":"uri"},"previous_secret_expires_at":{"type":"string","format":"date-time","description":"Set after a `rotate-secret`: until this instant deliveries are signed with both the new and the previous secret."}}},"status":{"anyOf":[{"type":"string","enum":["healthy"]},{"type":"string","enum":["failing"]}],"description":"`failing` after several consecutive failed delivery attempts; the next successful delivery (or a successful `test`) sets it back to `healthy`. A failing subscription keeps receiving deliveries: it is never deactivated automatically."},"consecutive_failures":{"type":"number","description":"Failed delivery attempts since the last success."},"last_failure_at":{"type":"string","format":"date-time"},"last_success_at":{"type":"string","format":"date-time"},"secret":{"type":"string","description":"The signing secret. Returned ONLY in the response of create (and of `rotate-secret`); get and list never include it. Store it when you create the subscription."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionWebhookMetadata","default":{}},"createdAt":{"type":"string","description":"NotificationSubscriptionWebhook creation date"},"updatedAt":{"type":"string","description":"NotificationSubscriptionWebhook updated date"}},"description":"NotificationSubscriptionWebhook"}],"description":"NotificationSubscription"},"NotificationSubscriptionEmail":{"type":"object","required":["id","events","channel","active","config","account","createdAt"],"properties":{"id":{"type":"string","description":"NotificationSubscriptionEmail ID"},"events":{"type":"array","items":{"type":"string","description":"Events to subscribe to (e.g., user.created)"}},"channel":{"type":"string","enum":["email"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["recipients"],"properties":{"recipients":{"type":"array","items":{"type":"string","description":"List of email addresses receiving notifications","format":"email"}}},"additionalProperties":false},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionEmailMetadata","default":{}},"createdAt":{"type":"string","description":"NotificationSubscriptionEmail creation date"},"updatedAt":{"type":"string","description":"NotificationSubscriptionEmail updated date"}},"description":"NotificationSubscriptionEmail"},"NotificationSubscriptionEmailConfig":{"type":"object","required":["recipients"],"properties":{"recipients":{"type":"array","items":{"type":"string","description":"List of email addresses receiving notifications","format":"email"}}},"additionalProperties":false},"NotificationSubscriptionEmailMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionEmailMetadata","default":{}},"NotificationSubscriptionWebhook":{"type":"object","required":["id","events","channel","active","config","account","createdAt"],"properties":{"id":{"type":"string","description":"NotificationSubscriptionWebhook ID"},"events":{"type":"array","items":{"type":"string"},"description":"Events to subscribe to. For `webhook` subscriptions it must be a subset of the published catalog; any other value is rejected with a 400:\n\n- `user.created`: A user was created (signup, import or Management API).\n- `user.updated`: A user was updated.\n- `user.deleted`: A user was deleted.\n- `auth.login`: A user logged in.\n- `credentials.exported`: Someone exported the tenant's password hashes (`GET /user/export` with hashes).\n- `team.created`: A team was created.\n- `team.updated`: A team was updated.\n- `team.deleted`: A team was deleted.\n- `team.member.added`: A user was added to a team.\n- `team.member.updated`: A team member's roles changed.\n- `team.member.removed`: A user was removed from a team (or left it).\n- `team.invite.created`: An invitation to join a team was created.\n- `team.invite.revoked`: A pending team invitation was revoked.\n- `team.invite.accepted`: A team invitation was accepted.\n- `organization.created`: An organization was created.\n- `organization.updated`: An organization was updated (name, settings or admins).\n- `organization.deleted`: An organization was deleted. Its teams are no longer in any organization; no `team.updated` is sent for them.\n\n`team.*` and `organization.*` payloads are notices, not state: they carry ids (`team_id`, `organization_id`, `user_id`, `roles`, `invite_id`...) and the receiver reads whatever else it needs. Use the delivery id (`x-faable-delivery`) to deduplicate: retries keep it."},"channel":{"type":"string","enum":["webhook"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Webhook URL","format":"uri"},"previous_secret_expires_at":{"type":"string","format":"date-time","description":"Set after a `rotate-secret`: until this instant deliveries are signed with both the new and the previous secret."}}},"status":{"anyOf":[{"type":"string","enum":["healthy"]},{"type":"string","enum":["failing"]}],"description":"`failing` after several consecutive failed delivery attempts; the next successful delivery (or a successful `test`) sets it back to `healthy`. A failing subscription keeps receiving deliveries: it is never deactivated automatically."},"consecutive_failures":{"type":"number","description":"Failed delivery attempts since the last success."},"last_failure_at":{"type":"string","format":"date-time"},"last_success_at":{"type":"string","format":"date-time"},"secret":{"type":"string","description":"The signing secret. Returned ONLY in the response of create (and of `rotate-secret`); get and list never include it. Store it when you create the subscription."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionWebhookMetadata","default":{}},"createdAt":{"type":"string","description":"NotificationSubscriptionWebhook creation date"},"updatedAt":{"type":"string","description":"NotificationSubscriptionWebhook updated date"}},"description":"NotificationSubscriptionWebhook"},"NotificationSubscriptionWebhookConfig":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Webhook URL","format":"uri"},"previous_secret_expires_at":{"type":"string","format":"date-time","description":"Set after a `rotate-secret`: until this instant deliveries are signed with both the new and the previous secret."}}},"NotificationSubscriptionWebhookMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionWebhookMetadata","default":{}},"NotificationSubscriptionCreate":{"anyOf":[{"type":"object","required":["events","channel","config"],"properties":{"events":{"type":"array","items":{"type":"string","description":"Events to subscribe to (e.g., user.created)"}},"channel":{"type":"string","enum":["email"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["recipients"],"properties":{"recipients":{"type":"array","items":{"type":"string","description":"List of email addresses receiving notifications","format":"email"}}},"additionalProperties":false},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"NotificationSubscriptionEmailCreate","additionalProperties":false},{"type":"object","required":["events","channel","config"],"properties":{"events":{"type":"array","items":{"type":"string"},"description":"Events to subscribe to. For `webhook` subscriptions it must be a subset of the published catalog; any other value is rejected with a 400:\n\n- `user.created`: A user was created (signup, import or Management API).\n- `user.updated`: A user was updated.\n- `user.deleted`: A user was deleted.\n- `auth.login`: A user logged in.\n- `credentials.exported`: Someone exported the tenant's password hashes (`GET /user/export` with hashes).\n- `team.created`: A team was created.\n- `team.updated`: A team was updated.\n- `team.deleted`: A team was deleted.\n- `team.member.added`: A user was added to a team.\n- `team.member.updated`: A team member's roles changed.\n- `team.member.removed`: A user was removed from a team (or left it).\n- `team.invite.created`: An invitation to join a team was created.\n- `team.invite.revoked`: A pending team invitation was revoked.\n- `team.invite.accepted`: A team invitation was accepted.\n- `organization.created`: An organization was created.\n- `organization.updated`: An organization was updated (name, settings or admins).\n- `organization.deleted`: An organization was deleted. Its teams are no longer in any organization; no `team.updated` is sent for them.\n\n`team.*` and `organization.*` payloads are notices, not state: they carry ids (`team_id`, `organization_id`, `user_id`, `roles`, `invite_id`...) and the receiver reads whatever else it needs. Use the delivery id (`x-faable-delivery`) to deduplicate: retries keep it."},"channel":{"type":"string","enum":["webhook"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Webhook URL. Must be `https`. Deliveries to private, loopback, link-local or metadata addresses are refused at delivery time.","format":"uri"},"secret":{"type":"string","minLength":1,"description":"Secret used to sign the webhook payload. Optional: when omitted one is generated and returned once, in the `secret` field of the response."}},"additionalProperties":false},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"NotificationSubscriptionWebhookCreate","additionalProperties":false}],"description":"NotificationSubscriptionCreate"},"NotificationSubscriptionEmailCreate":{"type":"object","required":["events","channel","config"],"properties":{"events":{"type":"array","items":{"type":"string","description":"Events to subscribe to (e.g., user.created)"}},"channel":{"type":"string","enum":["email"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["recipients"],"properties":{"recipients":{"type":"array","items":{"type":"string","description":"List of email addresses receiving notifications","format":"email"}}},"additionalProperties":false},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"NotificationSubscriptionEmailCreate","additionalProperties":false},"NotificationSubscriptionEmailConfigCreate":{"type":"object","required":["recipients"],"properties":{"recipients":{"type":"array","items":{"type":"string","description":"List of email addresses receiving notifications","format":"email"}}},"additionalProperties":false},"NotificationSubscriptionEmailMetadataCreate":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"},"NotificationSubscriptionWebhookCreate":{"type":"object","required":["events","channel","config"],"properties":{"events":{"type":"array","items":{"type":"string"},"description":"Events to subscribe to. For `webhook` subscriptions it must be a subset of the published catalog; any other value is rejected with a 400:\n\n- `user.created`: A user was created (signup, import or Management API).\n- `user.updated`: A user was updated.\n- `user.deleted`: A user was deleted.\n- `auth.login`: A user logged in.\n- `credentials.exported`: Someone exported the tenant's password hashes (`GET /user/export` with hashes).\n- `team.created`: A team was created.\n- `team.updated`: A team was updated.\n- `team.deleted`: A team was deleted.\n- `team.member.added`: A user was added to a team.\n- `team.member.updated`: A team member's roles changed.\n- `team.member.removed`: A user was removed from a team (or left it).\n- `team.invite.created`: An invitation to join a team was created.\n- `team.invite.revoked`: A pending team invitation was revoked.\n- `team.invite.accepted`: A team invitation was accepted.\n- `organization.created`: An organization was created.\n- `organization.updated`: An organization was updated (name, settings or admins).\n- `organization.deleted`: An organization was deleted. Its teams are no longer in any organization; no `team.updated` is sent for them.\n\n`team.*` and `organization.*` payloads are notices, not state: they carry ids (`team_id`, `organization_id`, `user_id`, `roles`, `invite_id`...) and the receiver reads whatever else it needs. Use the delivery id (`x-faable-delivery`) to deduplicate: retries keep it."},"channel":{"type":"string","enum":["webhook"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Webhook URL. Must be `https`. Deliveries to private, loopback, link-local or metadata addresses are refused at delivery time.","format":"uri"},"secret":{"type":"string","minLength":1,"description":"Secret used to sign the webhook payload. Optional: when omitted one is generated and returned once, in the `secret` field of the response."}},"additionalProperties":false},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"NotificationSubscriptionWebhookCreate","additionalProperties":false},"NotificationSubscriptionWebhookConfigCreate":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Webhook URL. Must be `https`. Deliveries to private, loopback, link-local or metadata addresses are refused at delivery time.","format":"uri"},"secret":{"type":"string","minLength":1,"description":"Secret used to sign the webhook payload. Optional: when omitted one is generated and returned once, in the `secret` field of the response."}},"additionalProperties":false},"NotificationSubscriptionWebhookMetadataCreate":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"},"NotificationSubscriptionUpdate":{"anyOf":[{"type":"object","properties":{"events":{"type":"array","items":{"type":"string","description":"Events to subscribe to (e.g., user.created)"}},"channel":{"type":"string","enum":["email"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","properties":{"recipients":{"type":"array","items":{"type":"string","description":"List of email addresses receiving notifications","format":"email"}}}},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Metadata update"}}},{"type":"object","properties":{"events":{"type":"array","items":{"type":"string"},"description":"Events to subscribe to. For `webhook` subscriptions it must be a subset of the published catalog; any other value is rejected with a 400:\n\n- `user.created`: A user was created (signup, import or Management API).\n- `user.updated`: A user was updated.\n- `user.deleted`: A user was deleted.\n- `auth.login`: A user logged in.\n- `credentials.exported`: Someone exported the tenant's password hashes (`GET /user/export` with hashes).\n- `team.created`: A team was created.\n- `team.updated`: A team was updated.\n- `team.deleted`: A team was deleted.\n- `team.member.added`: A user was added to a team.\n- `team.member.updated`: A team member's roles changed.\n- `team.member.removed`: A user was removed from a team (or left it).\n- `team.invite.created`: An invitation to join a team was created.\n- `team.invite.revoked`: A pending team invitation was revoked.\n- `team.invite.accepted`: A team invitation was accepted.\n- `organization.created`: An organization was created.\n- `organization.updated`: An organization was updated (name, settings or admins).\n- `organization.deleted`: An organization was deleted. Its teams are no longer in any organization; no `team.updated` is sent for them.\n\n`team.*` and `organization.*` payloads are notices, not state: they carry ids (`team_id`, `organization_id`, `user_id`, `roles`, `invite_id`...) and the receiver reads whatever else it needs. Use the delivery id (`x-faable-delivery`) to deduplicate: retries keep it."},"channel":{"type":"string","enum":["webhook"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","properties":{"url":{"type":"string","description":"Webhook URL. Must be `https`.","format":"uri"},"secret":{"type":"string","minLength":1,"description":"Replaces the signing secret immediately, with no overlap. Prefer `POST /notificationsubscription/{id}/rotate-secret`, which keeps the previous secret valid for 24 hours."}}},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Metadata update"}}}],"description":"NotificationSubscriptionUpdate"},"NotificationSubscriptionEmailMetadataUpdate":{"type":"object","properties":{},"additionalProperties":true,"description":"Metadata update"},"NotificationSubscriptionWebhookMetadataUpdate":{"type":"object","properties":{},"additionalProperties":true,"description":"Metadata update"},"WebhookTeamPayload":{"type":"object","required":["team_id"],"properties":{"team_id":{"type":"string"}},"description":"A notice: read the team with `GET /team/:team_id`."},"WebhookTeamMemberPayload":{"type":"object","required":["team_id","user_id"],"properties":{"team_id":{"type":"string"},"user_id":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}},"method":{"type":"string","description":"`team.member.added`: how they got in (`direct`, `invite_auto`, `invite_accepted`, `domain`)."},"previous_roles":{"type":"array","items":{"type":"string"},"description":"`team.member.updated`: the roles before the change."}}},"WebhookTeamInvitePayload":{"type":"object","required":["team_id"],"properties":{"team_id":{"type":"string"},"invite_id":{"type":"string"},"email":{"type":"string"},"user_id":{"type":"string"},"inviter_user_id":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}},"reason":{"type":"string","description":"`team.invite.revoked`: `revoked` by someone, `replaced` by a re-invite, or `declined` by the invitee."}}},"WebhookOrganizationPayload":{"type":"object","required":["organization_id"],"properties":{"organization_id":{"type":"string"}},"description":"A notice: read the organization with `GET /organization/:organization_id`."},"WebhookCredentialsExportedPayload":{"type":"object","required":["account","connection","exported_at"],"properties":{"account":{"type":"string"},"actor_user_id":{"type":"string"},"actor_client_id":{"type":"string"},"connection":{"anyOf":[{"type":"string"},{"type":"null"}]},"exported_at":{"type":"string","format":"date-time"}}},"WebhookEventUserCreated":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["user.created"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/User"}},"description":"A user was created (signup, import or Management API)."},"WebhookEventUserUpdated":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["user.updated"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/User"}},"description":"A user was updated."},"WebhookEventUserDeleted":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["user.deleted"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/User"}},"description":"A user was deleted."},"WebhookEventAuthLogin":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["auth.login"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/User"}},"description":"A user logged in."},"WebhookEventCredentialsExported":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["credentials.exported"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/WebhookCredentialsExportedPayload"}},"description":"Someone exported the tenant's password hashes (`GET /user/export` with hashes)."},"WebhookEventTeamCreated":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["team.created"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/WebhookTeamPayload"}},"description":"A team was created."},"WebhookEventTeamUpdated":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["team.updated"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/WebhookTeamPayload"}},"description":"A team was updated."},"WebhookEventTeamDeleted":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["team.deleted"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/WebhookTeamPayload"}},"description":"A team was deleted."},"WebhookEventTeamMemberAdded":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["team.member.added"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/WebhookTeamMemberPayload"}},"description":"A user was added to a team."},"WebhookEventTeamMemberUpdated":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["team.member.updated"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/WebhookTeamMemberPayload"}},"description":"A team member's roles changed."},"WebhookEventTeamMemberRemoved":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["team.member.removed"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/WebhookTeamMemberPayload"}},"description":"A user was removed from a team (or left it)."},"WebhookEventTeamInviteCreated":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["team.invite.created"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/WebhookTeamInvitePayload"}},"description":"An invitation to join a team was created."},"WebhookEventTeamInviteRevoked":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["team.invite.revoked"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/WebhookTeamInvitePayload"}},"description":"A pending team invitation was revoked."},"WebhookEventTeamInviteAccepted":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["team.invite.accepted"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/WebhookTeamInvitePayload"}},"description":"A team invitation was accepted."},"WebhookEventOrganizationCreated":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["organization.created"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/WebhookOrganizationPayload"}},"description":"An organization was created."},"WebhookEventOrganizationUpdated":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["organization.updated"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/WebhookOrganizationPayload"}},"description":"An organization was updated (name, settings or admins)."},"WebhookEventOrganizationDeleted":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["organization.deleted"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"$ref":"#/components/schemas/WebhookOrganizationPayload"}},"description":"An organization was deleted. Its teams are no longer in any organization; no `team.updated` is sent for them."},"WebhookEventPing":{"type":"object","required":["id","type","produced_at","payload"],"properties":{"id":{"type":"string","description":"The event id. Also the `x-faable-delivery` header, and the same on every retry: deduplicate by it."},"type":{"type":"string","enum":["ping"]},"produced_at":{"type":"number","description":"When the event happened, in Unix milliseconds."},"payload":{"type":"object","properties":{}}},"description":"Sent by `POST /notificationsubscription/:id/test` only, to check a receiver."},"WebhookEventType":{"anyOf":[{"type":"string","enum":["user.created"]},{"type":"string","enum":["user.updated"]},{"type":"string","enum":["user.deleted"]},{"type":"string","enum":["auth.login"]},{"type":"string","enum":["credentials.exported"]},{"type":"string","enum":["team.created"]},{"type":"string","enum":["team.updated"]},{"type":"string","enum":["team.deleted"]},{"type":"string","enum":["team.member.added"]},{"type":"string","enum":["team.member.updated"]},{"type":"string","enum":["team.member.removed"]},{"type":"string","enum":["team.invite.created"]},{"type":"string","enum":["team.invite.revoked"]},{"type":"string","enum":["team.invite.accepted"]},{"type":"string","enum":["organization.created"]},{"type":"string","enum":["organization.updated"]},{"type":"string","enum":["organization.deleted"]},{"type":"string","enum":["ping"]}]},"WebhookEvent":{"anyOf":[{"$ref":"#/components/schemas/WebhookEventUserCreated"},{"$ref":"#/components/schemas/WebhookEventUserUpdated"},{"$ref":"#/components/schemas/WebhookEventUserDeleted"},{"$ref":"#/components/schemas/WebhookEventAuthLogin"},{"$ref":"#/components/schemas/WebhookEventCredentialsExported"},{"$ref":"#/components/schemas/WebhookEventTeamCreated"},{"$ref":"#/components/schemas/WebhookEventTeamUpdated"},{"$ref":"#/components/schemas/WebhookEventTeamDeleted"},{"$ref":"#/components/schemas/WebhookEventTeamMemberAdded"},{"$ref":"#/components/schemas/WebhookEventTeamMemberUpdated"},{"$ref":"#/components/schemas/WebhookEventTeamMemberRemoved"},{"$ref":"#/components/schemas/WebhookEventTeamInviteCreated"},{"$ref":"#/components/schemas/WebhookEventTeamInviteRevoked"},{"$ref":"#/components/schemas/WebhookEventTeamInviteAccepted"},{"$ref":"#/components/schemas/WebhookEventOrganizationCreated"},{"$ref":"#/components/schemas/WebhookEventOrganizationUpdated"},{"$ref":"#/components/schemas/WebhookEventOrganizationDeleted"},{"$ref":"#/components/schemas/WebhookEventPing"}],"description":"The JSON body of a webhook delivery. Verify `x-faable-signature` before trusting it: HMAC-SHA256 of `${x-faable-timestamp}.${raw body}` with the subscription secret."},"Log":{"type":"object","required":["id","type","status","account","createdAt"],"properties":{"id":{"type":"string","description":"Log ID"},"type":{"type":"string","description":"Log type, e.g. email.user.created"},"status":{"anyOf":[{"type":"string","enum":["success"]},{"type":"string","enum":["failed"]},{"type":"string","enum":["skipped"]},{"type":"string","enum":["info"]}],"description":"Log status"},"message":{"type":"string","description":"Optional log message"},"data":{"description":"Type-specific structured payload"},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"client":{"anyOf":[{"$ref":"#/components/schemas/Client"},{"type":"string"},{}]},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"team":{"anyOf":[{"$ref":"#/components/schemas/Team"},{"type":"string"},{}]},"identity":{"anyOf":[{"$ref":"#/components/schemas/Identity"},{"type":"string"},{}]},"ticket":{"type":"string"},"expires_at":{"type":"string","format":"date-time","description":"When this log row will be auto-deleted by the Mongo TTL monitor. Absent on rows recorded without `ttl_seconds`."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"LogMetadata","default":{}},"createdAt":{"type":"string","description":"Log creation date"},"updatedAt":{"type":"string","description":"Log updated date"}},"description":"Log"},"LogMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"LogMetadata","default":{}},"Session":{"type":"object","required":["id","user","sid","status","last_seen_at","expires_at","account","createdAt"],"properties":{"id":{"type":"string","description":"Session ID"},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"sid":{"type":"string","description":"OIDC session id. Every id_token minted for this session carries it as the `sid` claim; a back-channel or front-channel logout names the session with it."},"status":{"anyOf":[{"type":"string","enum":["active"]},{"type":"string","enum":["revoked"]}],"description":"`active` or `revoked`. An active session whose `expires_at` is in the past has expired: nothing can refresh it any more, but the row stays for the history."},"ip":{"type":"string","description":"Client IP at the last authentication."},"user_agent":{"type":"string","description":"Raw `User-Agent` header at the last authentication."},"language":{"type":"string","description":"First language tag of the browser's `Accept-Language` at the last authentication, e.g. `es-ES`."},"device_name":{"type":"string","description":"The name a device gave itself when it asked for a code (device authorization grant), e.g. the hostname `faable login` runs on. Absent for browser sessions: a browser does not expose one."},"client":{"anyOf":[{"$ref":"#/components/schemas/Client"},{"type":"string"},{}]},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"issuer":{"type":"string","description":"Issuer URL the session was minted on."},"last_seen_at":{"type":"string","format":"date-time","description":"Last authentication or token refresh through this session. Each refresh slides `expires_at` 30 days from here."},"expires_at":{"type":"string","format":"date-time","description":"When the session stops being usable unless something refreshes it first."},"revoked_at":{"type":"string","format":"date-time"},"revoked_reason":{"anyOf":[{"type":"string","enum":["admin"]},{"type":"string","enum":["logout"]},{"type":"string","enum":["user"]},{"type":"string","enum":["password_change"]}],"description":"`admin`: revoked through the Management API. `logout`: the user signed out. `user`: the user revoked it from another device. `password_change`: the user set or changed their password, which ends every other session."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"SessionMetadata","default":{}},"createdAt":{"type":"string","description":"Session creation date"},"updatedAt":{"type":"string","description":"Session updated date"}},"description":"Session"},"SessionMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"SessionMetadata","default":{}},"Customdomain":{"type":"object","required":["id","domain","token","status","attempts","account","createdAt"],"properties":{"id":{"type":"string","description":"Customdomain ID"},"domain":{"type":"string"},"token":{"type":"string"},"status":{"enum":["PENDING","VERIFYING","ACTIVE","FAILED","DEGRADED"]},"attempts":{"type":"number"},"nextCheck":{"type":"string","format":"date-time"},"lastCheck":{"type":"string","format":"date-time"},"errorLog":{"type":"string","description":"User feedback error from last check"},"verifiedAt":{"type":"string","format":"date-time"},"records":{"type":"array","items":{"type":"object","required":["type","host","expected","observed","ok"],"properties":{"type":{"type":"string"},"host":{"type":"string"},"expected":{"type":"string"},"observed":{"type":"array","items":{"type":"string"}},"ok":{"type":"boolean"}}},"description":"Per-record DNS status from the last check: what each record points to now vs. expected"},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"CustomdomainMetadata","default":{}},"createdAt":{"type":"string","description":"Customdomain creation date"},"updatedAt":{"type":"string","description":"Customdomain updated date"}},"description":"Customdomain"},"CustomdomainMetadata":{"type":"object","properties":{},"additionalProperties":true,"description":"CustomdomainMetadata","default":{}},"CustomdomainCreate":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"CustomdomainCreate","additionalProperties":false},"CustomdomainMetadataCreate":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"},"ClientRegistrationRequest":{"type":"object","required":["redirect_uris"],"properties":{"redirect_uris":{"type":"array","items":{"type":"string","format":"uri"},"description":"Array of redirection URI values used by the client (RFC 7591 §2). REQUIRED for any client that uses an authorization grant.","minItems":1},"client_name":{"type":"string","description":"Human-readable name of the client (RFC 7591 §2)"},"client_uri":{"type":"string","description":"URL of the home page of the client"},"logo_uri":{"type":"string"},"tos_uri":{"type":"string"},"policy_uri":{"type":"string"},"contacts":{"type":"array","items":{"type":"string"},"description":"Array of email addresses responsible for the client"},"grant_types":{"type":"array","items":{"type":"string"},"description":"Grant types the client will use. Defaults to [\"authorization_code\"]. Must be a subset of what the server supports."},"response_types":{"type":"array","items":{"type":"string"},"description":"Response types the client will use. Defaults to [\"code\"]."},"token_endpoint_auth_method":{"type":"string","description":"How the client authenticates at the token endpoint. Defaults to \"client_secret_basic\"."},"application_type":{"anyOf":[{"type":"string","enum":["web"]},{"type":"string","enum":["native"]}],"description":"OIDC §2 — application type. Defaults to \"web\"."},"post_logout_redirect_uris":{"type":"array","items":{"type":"string"},"description":"OIDC RP-Initiated Logout — URIs the OP MAY redirect to after end-session."},"scope":{"type":"string","description":"Space-separated list of scope values the client may use."},"software_id":{"type":"string"},"software_version":{"type":"string"},"frontchannel_logout_uri":{"type":"string","description":"OIDC Front-Channel Logout 1.0 — URL the OP loads inside an iframe at end-session to let the RP clear its session."},"frontchannel_logout_session_required":{"type":"boolean","description":"OIDC Front-Channel Logout 1.0 — when true, the OP MUST include `iss` and `sid` parameters when calling the frontchannel_logout_uri."},"backchannel_logout_uri":{"type":"string","description":"OIDC Back-Channel Logout 1.0 — URL the OP POSTs a signed `logout_token` to when a session this client took part in ends."},"backchannel_logout_session_required":{"type":"boolean","description":"OIDC Back-Channel Logout 1.0 — when true, the `logout_token` MUST include a `sid` claim."},"web_origins":{"type":"array","items":{"type":"string"},"description":"Non-standard (Auth0-compatible) — origins (scheme://host[:port], no path) allowed to make cross-origin (CORS) requests to the token and passwordless endpoints. Empty allows any origin."}},"additionalProperties":true},"ClientRegistrationResponse":{"type":"object","required":["client_id","client_id_issued_at","client_secret_expires_at","redirect_uris","grant_types","response_types","token_endpoint_auth_method","application_type"],"properties":{"client_id":{"type":"string"},"client_secret":{"type":"string"},"client_id_issued_at":{"type":"number","description":"Unix timestamp (seconds) at which the client_id was issued"},"client_secret_expires_at":{"type":"number","description":"0 if the client_secret does not expire"},"redirect_uris":{"type":"array","items":{"type":"string"}},"client_name":{"type":"string"},"client_uri":{"type":"string"},"logo_uri":{"type":"string"},"tos_uri":{"type":"string"},"policy_uri":{"type":"string"},"contacts":{"type":"array","items":{"type":"string"}},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string"},"application_type":{"type":"string"},"post_logout_redirect_uris":{"type":"array","items":{"type":"string"}},"scope":{"type":"string"},"software_id":{"type":"string"},"software_version":{"type":"string"},"frontchannel_logout_uri":{"type":"string"},"frontchannel_logout_session_required":{"type":"boolean"},"backchannel_logout_uri":{"type":"string"},"backchannel_logout_session_required":{"type":"boolean"},"web_origins":{"type":"array","items":{"type":"string"}}},"additionalProperties":true},"AuthAccountCreate":{"type":"object","required":["name","team"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100},"team":{"type":"string"},"logo_src":{"type":"string"},"icon_src":{"type":"string"},"callback_hostnames":{"type":"array","items":{"type":"string"}},"default_connection":{"type":"string"},"tags":{"type":"array","items":{"type":"string","maxLength":50},"maxItems":20},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"AuthAccountCreate","additionalProperties":false},"AuthAccountMetadataCreate":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"},"AccountTransferBody":{"type":"object","required":["to_project","reason"],"properties":{"to_project":{"type":"string","description":"Destination project (`project_<hex>`) or its team id (`team_<hex>`) — both spellings are accepted."},"reason":{"type":"string","minLength":3,"maxLength":500,"description":"Why the tenant is being moved. Recorded in the tenant's log and in ours."},"dry_run":{"type":"boolean","default":false,"description":"Return the plan (what rides along + blockers) without writing."},"allow_over_cap":{"type":"boolean","default":false,"description":"Move even if the destination ends up above the per-project cap on Auth Accounts."}},"additionalProperties":false},"AccountTransferResponse":{"type":"object","required":["moved","account_id","account_name","slug","domain","from_project","to_project","target_tenants_before","moves_with_it","blockers"],"properties":{"moved":{"type":"boolean"},"account_id":{"type":"string"},"account_name":{"type":"string"},"slug":{"type":"string"},"domain":{"type":"string"},"from_project":{"type":"string"},"to_project":{"type":"string"},"target_tenants_before":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"moves_with_it":{"type":"object","additionalProperties":{"type":"number"}},"blockers":{"type":"array","items":{"type":"object","required":["code","detail"],"properties":{"code":{"type":"string"},"detail":{"type":"string"}}}},"tenants_left_in_source":{"type":"number"},"tenants_in_target":{"type":"number"}}},"AccountEmailSenderParams":{"type":"object","required":["account_id"],"properties":{"account_id":{"type":"string"}},"additionalProperties":false},"AccountEmailSenderBody":{"type":"object","required":["address"],"properties":{"address":{"type":"string","format":"email","maxLength":320,"nullable":true}},"additionalProperties":false},"AuthAccountUpdate":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"logo_src":{"type":"string","nullable":true},"icon_src":{"type":"string","nullable":true},"callback_hostnames":{"type":"array","items":{"type":"string"}},"default_connection":{"type":"string","nullable":true},"enabled_locales":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string","maxLength":50},"maxItems":20},"notification_settings":{"type":"object","properties":{"welcome_email_enabled":{"type":"boolean","description":"Send the built-in welcome email on user.created","default":false},"verify_email_auto_send":{"type":"boolean","description":"Send a verification email automatically when a user is created with `email_verified=false` and an email address. The link in the email lands on `GET /verify-email?ticket=...` and flips `email_verified=true` with `email_verified_method=verification_flow`. When `false`, verification emails must be requested explicitly via `POST /user/:id/verify-email/start`.","default":false}}},"email_reply_to":{"type":"string","format":"email","maxLength":320,"nullable":true},"email_from_name":{"type":"string","minLength":1,"maxLength":100,"nullable":true},"welcome_email":{"anyOf":[{"type":"object","properties":{"cta_url":{"type":"string","format":"uri","maxLength":500,"description":"Where the welcome button sends the user. Defaults to `https://<first callback hostname>`. Point it at your app (not your marketing site) so a fresh user lands somewhere useful.","nullable":true},"body":{"type":"string","maxLength":600,"description":"Replaces the default one-line body (\"Your account on … is ready to go.\"). Plain text, sent verbatim in every enabled locale.","nullable":true},"social_links":{"type":"array","items":{"type":"object","required":["label","url"],"properties":{"label":{"type":"string","minLength":1,"maxLength":40},"url":{"type":"string","format":"uri","maxLength":500}},"additionalProperties":false},"maxItems":6,"description":"Rendered as a \"Follow <account>\" block after the button — GitHub, LinkedIn, X, YouTube… Empty or absent hides the block.","nullable":true}},"additionalProperties":false},{"type":"null"}]},"login_methods":{"anyOf":[{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},{"type":"null"}]},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"mfa_policy":{"anyOf":[{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},{"type":"null"}]},"recovery_channels":{"anyOf":[{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},{"type":"null"}]},"default_country_iso":{"anyOf":[{"type":"string","minLength":2,"maxLength":2},{"type":"null"}]},"webauthn_rp_id":{"type":"string","nullable":true},"logout_confirm_required":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"dynamic_client_registration_enabled":{"type":"boolean","nullable":true},"client_id_metadata_documents_enabled":{"type":"boolean","nullable":true},"oauth21_enabled":{"type":"boolean","nullable":true},"oauth_link_verified_email":{"type":"boolean","nullable":true},"canonical_host":{"type":"string","nullable":true},"login_flow":{"type":"string","nullable":true}},"description":"AuthAccountUpdate","additionalProperties":false},"FactorSummary":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string"},"type":{"type":"string"},"name":{"type":"string"},"confirmed_at":{"type":"string"},"last_used_at":{"type":"string"},"remaining":{"type":"integer"}}},"OAuthTokenParams":{"type":"object","properties":{"grant_type":{"anyOf":[{"type":"string","enum":["authorization_code"]},{"type":"string","enum":["client_credentials"]},{"type":"string","enum":["refresh_token"]},{"type":"string","enum":["password"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-otp"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-recovery-code"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/passwordless/otp"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:token-exchange"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:device_code"]}],"description":"Grant Type. https://oauth.net/2/grant-types/"},"client_id":{"type":"string"},"client_secret":{"type":"string"},"scope":{"type":"string"},"code":{"type":"string"},"code_verifier":{"type":"string"},"username":{"type":"string"},"password":{"type":"string"},"realm":{"type":"string","description":"For `grant_type=password`: the name of the database connection to sign in against. Defaults to the first database connection offered to the client."},"otp":{"type":"string"},"refresh_token":{"type":"string"},"mfa_token":{"type":"string","description":"Opaque token returned with a `403 mfa_required` response. Present it here with `grant_type=http://auth0.com/oauth/grant-type/mfa-otp` (or `…/mfa-recovery-code`) and the code, to finish a grant a second-factor policy interrupted."},"recovery_code":{"type":"string","description":"One of the user's one-time recovery codes, for `grant_type=http://auth0.com/oauth/grant-type/mfa-recovery-code`."},"subject_token":{"type":"string"},"subject_token_type":{"type":"string"},"device_code":{"type":"string","description":"The device code returned by the authorization server."},"audience":{"type":"string","description":"Auth0-compatible alias for `resource` (RFC 8707). When both are provided they must match."},"resource":{"type":"string","description":"RFC 8707 Resource Indicator. Absolute URI of the target Api, no fragment. Matched against `Api.identifier` in the tenant."},"invalidate_token":{"type":"string","description":"client_credentials only. An access token previously returned by this endpoint that a resource server rejected with 401. If it is the cached token for this client, the cache entry is dropped and a new token is issued; otherwise it has no effect."}},"additionalProperties":true},"OAuthGrantType":{"anyOf":[{"type":"string","enum":["authorization_code"]},{"type":"string","enum":["client_credentials"]},{"type":"string","enum":["refresh_token"]},{"type":"string","enum":["password"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-otp"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-recovery-code"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/passwordless/otp"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:token-exchange"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:device_code"]}],"description":"Grant Type. https://oauth.net/2/grant-types/"},"UsernamepasswordLoginBody":{"type":"object","required":["username","password","client_id"],"properties":{"username":{"type":"string"},"password":{"type":"string"},"state":{"type":"string","description":"An opaque value the applications adds to the initial request that the authorization server includes when redirecting the back to the application. This value must be used by the application to prevent CSRF attacks."},"response_type":{"type":"string"},"code_challenge":{"type":"string","description":"PKCE challenge, without a `state` only."},"code_challenge_method":{"type":"string","description":"Only `S256` is supported."},"redirect_to":{"type":"string","description":"Same-origin path to return the user to after authentication, instead of the OAuth client redirect. Used by first-party interactive flows (e.g. device-code confirm). Non same-origin values are ignored."},"remember_me":{"type":"boolean","description":"The \"Remember me on this device\" checkbox. Honoured only when the tenant shows one (`login_methods.remember_me: \"optional\"`): `true` keeps the session for `remember_me_days`, `false` ends it when the browser closes."},"client_id":{"type":"string"},"connection":{"type":"string","description":"Deprecated. Use connection_id"},"connection_id":{"type":"string","description":"Which oauth connection to use. Leave empty to use default connection"},"scope":{"type":"string"},"redirect_uri":{"type":"string","description":"The URL to which Faable will redirect the browser after authorization has been granted by the user."},"nonce":{"type":"string","description":"OIDC §3.1.2.1 — string value used to associate a Client session with an ID Token, to mitigate replay attacks. If passed at /authorize, the same value MUST appear in the issued id_token."},"max_age":{"type":"number","description":"OIDC §3.1.2.1 — maximum allowable elapsed time in seconds since the last End-User authentication. If exceeded, the OP re-authenticates and `auth_time` is REQUIRED in the resulting id_token.","minimum":0},"prompt":{"type":"string"},"login_hint":{"type":"string","description":"OIDC §3.1.2.1 — hint about the identifier (email, username) the End-User will sign in with. Pre-fills the hosted login screen and is forwarded to upstream identity providers."},"id_token_hint":{"type":"string","description":"OIDC §3.1.2.1 — an id_token this provider issued earlier for the user the client believes is signed in. Verified (signature, not expiry). With `prompt=none`, a session for a different user answers `login_required`; otherwise it forces a fresh sign-in."},"acr_values":{"type":"string","description":"OIDC §3.1.2.1 — space-separated Authentication Context Class References, in order of preference. `urn:faable:loa:2` asks for a second factor: a session that only has one is challenged (or, with `prompt=none`, answers `interaction_required`)."},"ui_locales":{"type":"string","description":"OIDC §3.1.2.1 — preferred languages for the hosted screens, space-separated BCP47 tags. Forwarded to upstream identity providers."},"audience":{"type":"string","description":"Auth0-compatible alias for `resource` (RFC 8707). When both are sent they must match. Identifier of the Api the client wants to access; persisted in the auth state so the token issued at /oauth/token targets the matching Api resource."},"resource":{"type":"string","description":"RFC 8707 Resource Indicator. MUST be an absolute URI without fragment. When matched against a registered Api in the tenant the issued access_token gets `aud = <api.identifier>`."}},"description":"UsernamepasswordLoginBody","additionalProperties":false},"LoginCallbackBody":{"type":"object","required":["wa","wresult","wctx"],"properties":{"wa":{"type":"string","description":"is always wsignin1.0"},"wresult":{"type":"string","description":"JWT"},"wctx":{"type":"string","description":"Serialized JSON with context"}}},"ErrorCode":{"type":"string","description":"Machine-readable error code. One HTTP status per code.\n\n- `bad_request` (400): The request is malformed. `message` says what is wrong.\n- `validation_error` (400): The body, query or path failed schema validation. `details.issues` lists each failing field.\n- `unauthorized` (401): The request carries no valid credentials.\n- `payment_required` (402): The feature is not included in the current plan.\n- `forbidden` (403): The credentials are valid but do not allow this operation.\n- `not_found` (404): The resource does not exist in this account.\n- `method_not_allowed` (405): The HTTP method is not supported on this path.\n- `conflict` (409): The request conflicts with the current state of the resource.\n- `last_sign_in_method` (409): Unlinking this account would leave the user with no way to sign in: set a password, verify the email or link another account first.\n- `payload_too_large` (413): The request body exceeds the size limit.\n- `unsupported_media_type` (415): The `Content-Type` is not accepted by this endpoint.\n- `unprocessable_entity` (422): The request is well-formed but cannot be processed.\n- `too_many_requests` (429): Rate limit exceeded. Honour the `Retry-After` header.\n- `internal_error` (500): Unexpected server error. Retry later; the request id is logged.\n- `already_exists` (400): A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n- `invalid_id` (400): The id in the path is not a valid id for this resource.\n- `account_not_found` (404): No Auth Account matches the request (domain, header or token).\n- `invalid_query` (400): The `?query=` FaableQL expression could not be parsed.\n- `search_not_supported` (400): This resource does not support `?search=`.\n- `invalid_expand` (400): An `?expand=` path is not allowed on this resource.\n- `access_denied` (403): The caller may not access this row (ownership or machine-to-machine restriction).\n- `insufficient_scope` (403): The token lacks a scope this operation requires. `message` names it.\n- `team_required` (400): An Auth Account must belong to a project (team).\n- `account_limit_reached` (409): The project already has the maximum number of Auth Accounts.\n- `invalid_project_id` (400): The project id is not of the form `project_<hex>` or `team_<hex>`.\n- `account_transfer_refused` (409): The Auth Account cannot be moved to that project. `details.blockers` lists why.\n- `email_sender_domain_not_allowed` (400): The sender address is not in a domain this platform signs email for (AUTH_EMAIL_SENDER_DOMAINS).\n- `plan_required` (402): The setting requires a higher plan. `message` says which.\n- `invalid_canonical_host` (400): `canonical_host` must be this account's own platform host or one of its verified custom domains.\n- `canonical_host_breaks_passkeys` (400): `canonical_host` is outside the account's current WebAuthn RP ID — every passkey already enrolled would stop working. Set `webauthn_rp_id` in the same request to a suffix that covers both.\n- `not_logged_in` (401): There is no signed-in session for this request.\n- `session_already_revoked` (409): The session was already revoked.\n- `invalid_token` (401): The bearer token is missing, malformed, expired or has no subject.\n- `not_owner` (403): The caller may only perform this operation on their own user.\n- `user_suspended` (403): The user is suspended and cannot sign in or be modified.\n- `invalid_credentials` (400): The email/username or password is incorrect.\n- `login_denied` (401): A post-login Action denied the sign-in. `message` carries the reason it gave. On token grants it is a 403 with `error: invalid_grant`.\n- `signup_denied` (403): A pre-signup Action or an active block denied the sign-up. `message` carries the reason.\n- `client_credentials_denied` (401): An Action denied the client_credentials grant. `message` carries the reason.\n- `m2m_quota_exceeded` (402): The project used the machine-to-machine tokens its plan includes this month. Free is capped; Hobby and Pro are billed per token instead.\n- `signup_disabled` (403): Self-service sign-up is disabled on this connection.\n- `email_taken` (409): Another user in this account already uses that email.\n- `username_taken` (409): Another user of this database connection already uses that username.\n- `invalid_username` (400): The username is empty, or contains an `@` or whitespace. `message` says which.\n- `invalid_email` (400): The email address is not valid.\n- `invalid_name` (400): The name contains a link. `message` says which field.\n- `same_email` (400): The new email is the same as the current one.\n- `user_has_no_email` (400): The operation needs an email but the user has none.\n- `password_too_weak` (400): The password does not meet the connection policy. `message` lists each unmet rule.\n- `invalid_password_hash` (400): The imported password hash cannot be read, or its cost is above what a login can afford. `message` names the field.\n- `weak_password_hash` (400): The imported password hash uses a fast digest (MD5, SHA-1, SHA-2) or a cost below the minimum. `message` says which.\n- `invalid_code` (400): The verification code is wrong, expired or already used.\n- `invalid_phone` (400): The phone number is not E.164 and could not be resolved with the account default country. Include the country code.\n- `phone_changed` (400): The phone on the user changed after the code was sent. Start again.\n- `phone_already_verified` (409): The user already has a verified phone; the number cannot be swapped from the screen.\n- `sms_unavailable` (409): This account cannot send SMS right now. The suffix says why: `sms_unavailable:plan`, `:quota`, `:provider`.\n- `sms_failed` (409): The SMS provider rejected the message.\n- `invalid_state` (400): The `state` is missing, expired, or does not describe a resumable step.\n- `state_mismatch` (401): The `state` belongs to another account, session, client or ceremony.\n- `invalid_ticket` (400): The ticket does not exist or is not the kind this endpoint accepts.\n- `ticket_used` (400): The ticket was already consumed.\n- `ticket_expired` (400): The ticket is past its expiry.\n- `ticket_not_found` (404): No ticket with that id belongs to this user.\n- `credential_not_found` (404): The user has no password credential on this connection.\n- `invalid_link` (400): The magic link is malformed.\n- `expired_link` (400): The magic link token was not found or has expired.\n- `invalid_otp` (401): The one-time password is wrong or expired.\n- `invalid_client` (400): The `client_id` in the request does not name a client of this account.\n- `client_not_found` (404): No client with that `client_id`.\n- `client_mismatch` (401): The client belongs to a different account.\n- `invalid_client_metadata` (400): A client field was rejected: by dynamic client registration (RFC 7591), or on a client create/update (`initiate_login_uri`). `message` names it.\n- `invalid_connection` (400): The connection does not exist, is disabled, or is not of the type this flow needs.\n- `no_database_connection` (400): The tenant has no database connection, so there is nowhere to store a password. Create one and retry.\n- `connection_required` (400): No connection was given and the client has no `default_connection`.\n- `connection_misconfigured` (400): The connection is missing settings needed to talk to its provider.\n- `passwordless_unavailable` (400): No passwordless connection is configured or enabled for this client.\n- `ambiguous_connection` (400): Several connections match; pass `connection_id`.\n- `invalid_redirect_uri` (400): The redirect URI is not registered for the client.\n- `invalid_saml_request` (400): The SAMLRequest is missing, too large, not well-formed or not a SAML 2.0 AuthnRequest.\n- `saml_sp_mismatch` (400): The AuthnRequest does not match the client's SAML configuration: its Issuer is not the configured entity_id, or it asks for another Assertion Consumer Service URL or binding.\n- `saml_not_enabled` (404): This client does not have SAML enabled.\n- `invalid_saml_sp` (400): The `saml_sp` configuration is invalid: `acs_url` must be an absolute https URL without a fragment.\n- `origin_not_allowed` (403): The request `Origin` is not in the client's Allowed Web Origins.\n- `audience_not_found` (403): The `audience` does not name an API registered in this account.\n- `system_resource` (403): The resource is managed by Faable and cannot be modified or deleted.\n- `invalid_request` (400): A required OAuth parameter is missing or two of them are incompatible. `message` says which.\n- `invalid_device_code` (400): The device or user code is invalid or expired.\n- `provider_error` (400): The upstream identity provider returned an error. The suffix is the provider’s own code, when it sent one: `provider_error:bad_refresh_token` (the user must re-authorize), `provider_error:incorrect_client_credentials` (our configuration, the user can do nothing). `details.provider_error_description` carries the provider’s sentence.\n- `identity_already_linked` (409): That external identity is already linked to another user.\n- `identity_link_denied` (409): That external identity cannot be linked to this user.\n- `identity_conflict` (409): The user already has a linked identity for this connection.\n- `identity_orphaned` (400): The identity pointed at a user that no longer exists; it was removed. Sign in again.\n- `no_provider_token` (400): The identity holds no provider access token.\n- `no_refresh_token` (400): The identity holds no provider refresh token, so it cannot be refreshed.\n- `action_unavailable` (401): The Action that paused this login is disabled or belongs elsewhere.\n- `login_flow_misconfigured` (500): The login flow of this account cannot run. `message` carries the node error.\n- `mfa_required` (403): The login needs a second factor that this flow cannot collect, or one is still pending on the session.\n- `step_up_required` (403): Confirm an existing factor before changing your factors.\n- `reauth_required` (403): The operation needs a recent sign-in (and the second factor, when the user has one). Sign in again with `max_age=0` and retry with the new token.\n- `invalid_current_password` (400): The current password is wrong.\n- `email_not_verified` (403): The user's email is not verified, and the operation needs it to be.\n- `invalid_profile_field` (400): A profile field is not declared for this application, or its value does not match the declaration. `message` names the key.\n- `interaction_required` (403): An Action asked for a redirect on a non-interactive flow.\n- `passkey_login_disabled` (400): Sign in with a passkey is not enabled for this client.\n- `mfa_pending` (401): A second-factor challenge is pending; answer it before continuing.\n- `mfa_invalid_code` (401): The authenticator code did not verify.\n- `mfa_invalid_recovery_code` (401): The recovery code did not verify.\n- `too_many_attempts` (429): Too many wrong codes. Wait a few minutes and try again.\n- `too_many_login_attempts` (429): Too many failed sign-in attempts for this email or username from this network. Wait a few minutes and try again.\n- `totp_not_allowed` (401): This account does not accept authenticator apps.\n- `no_usable_factor` (400): The user has no confirmed second factor to challenge.\n- `factor_not_found` (400): No factor with that id belongs to this user.\n- `factor_already_confirmed` (400): The factor was already confirmed.\n- `invalid_factor` (400): The factor cannot be verified (no secret material).\n- `session_missing` (401): The session that started this login no longer exists.\n- `passkey_verification_failed` (400): The WebAuthn response could not be verified.\n- `no_passkey_enrolled` (400): The user has no passkey to sign in with.\n- `malformed_credential` (400): The WebAuthn credential is malformed.\n- `unknown_passkey` (401): The passkey is not enrolled for this user.\n- `invalid_passkey` (401): The passkey assertion did not verify.\n- `invalid_invite_url` (400): An invite's `accept_url` or `redirect_uri` is not an http(s) URL at an origin of this tenant's client callbacks or `callback_hostnames`, or it carries a fragment.\n- `invite_email_mismatch` (403): The invite is for another email address than the accepting user's. `details.invited_email` shows it masked.\n- `invite_email_unverified` (403): The accepting user's email matches the invite but is not verified yet.\n- `invite_not_found` (404): No invite with that id in this account.\n- `invite_used` (400): The invite was already accepted.\n- `already_member` (400): The user is already a member of the team.\n- `not_a_member` (404): The user is not a member of the team.\n- `consent_options_unavailable` (502): The consent screen could not load the choices from the API being authorized (its `consent_options.url`). Try again in a moment.\n- `consent_choice_invalid` (400): The choice sent with the consent decision is not one the API offered for this user.\n- `oauth21_required` (400): The tenant enforces OAuth 2.1 (`oauth21_enabled`) and the request is one OAuth 2.1 removed: a `response_type` other than `code`, an authorization request without PKCE (`code_challenge` with `S256`), or the password grant. `message` says which. Update the application (with `@faable/auth-js`, version 3.1.0 or later) or turn `oauth21_enabled` off for the tenant.\n- `registration_disabled` (403): Dynamic client registration is turned off for this tenant. Create the client from the dashboard or the Management API, or turn on `dynamic_client_registration_enabled`.\n- `invalid_email_domain` (400): Not an email domain this organization can claim: it must be a plain domain name (`acme.com`), lowercase, without scheme, path or wildcard.\n- `public_email_domain` (400): A public email provider (gmail.com, outlook.com…) cannot belong to an organization: anyone can have an address there.\n- `organization_domain_not_found` (404): The organization has no such domain.\n- `organization_domain_taken` (409): Another organization of this tenant already verified this domain. A domain belongs to one organization.\n- `organization_domain_unverified` (400): The TXT record was not found or does not hold the expected value yet. `details.record` says what was expected and what resolves now; DNS can take a few minutes to propagate.\n- `plan_unavailable` (503): The plan could not be checked right now, and this setting is only for paid plans. Try again in a minute.\n- `organization_domain_mismatch` (403): This organization's identity provider can only sign in addresses of the organization's verified email domains.\n- `team_not_in_organization` (400): Auto-join can only put people in teams of the same organization. `details.teams` lists the others.\n- `organization_has_no_idp` (400): Requiring SSO needs an identity provider: bind an `oidc` connection to the organization first.\n- `organization_not_found` (404): No organization with that id in this account.\n- `invalid_user` (400): The user does not exist in this account.\n- `not_an_organization_admin` (404): The user is not an admin of the organization.\n- `last_organization_admin` (409): An organization keeps at least one admin. Add another admin before removing this one.\n- `team_id_unavailable` (409): The team id cannot be used in this tenant. Pick another id or let the API generate one.\n- `use_team_member_route` (400): This generic route cannot create memberships. Use `POST /team/{team_id}/member` for team members or `POST /role/{role_id}/users` for role grants.\n- `role_not_found` (404): No role with that id in this account.\n- `invalid_role` (400): The role belongs to another account.\n- `invalid_permission` (400): A permission is not in the catalog of its Api, or the Api is not in this account. `details.permissions` lists the ones refused.\n- `flow_not_found` (400): The login flow does not exist in this account.\n- `revision_not_found` (404): The revision is not in the flow history.\n- `invalid_webhook_event` (400): An event is not in the webhook event catalog. `message` and `details.valid_events` list the valid ones.\n- `invalid_webhook_url` (400): The webhook URL must be an absolute `https` URL.\n- `not_a_webhook` (400): The operation only applies to `webhook` subscriptions.\n- `delivery_not_found` (404): No webhook delivery with that id for this subscription.\n- `delivery_in_progress` (409): The delivery is being attempted right now; retry the redeliver in a minute.\n- `account_mismatch` (400): The `account_id` in the body is not the account the request is scoped to (`x-faableauth-account`).\n- `connect_redirect_uri_missing` (400): The client's callbacks do not include `<site_url>/wp-login.php?faable-auth=callback`.\n- `invalid_site_url` (400): `site_url` is not an absolute http(s) URL without query string or fragment.\n- `handoff_not_found` (404): No connect handoff matches that code.\n- `handoff_expired` (410): The connect handoff is past its expiry. Start again.\n- `handoff_used` (410): The connect handoff was already redeemed.\n- `invalid_code_verifier` (400): `code_verifier` does not match the `code_challenge` of the handoff.","enum":["bad_request","validation_error","unauthorized","payment_required","forbidden","not_found","method_not_allowed","conflict","last_sign_in_method","payload_too_large","unsupported_media_type","unprocessable_entity","too_many_requests","internal_error","already_exists","invalid_id","account_not_found","invalid_query","search_not_supported","invalid_expand","access_denied","insufficient_scope","team_required","account_limit_reached","invalid_project_id","account_transfer_refused","email_sender_domain_not_allowed","plan_required","invalid_canonical_host","canonical_host_breaks_passkeys","not_logged_in","session_already_revoked","invalid_token","not_owner","user_suspended","invalid_credentials","login_denied","signup_denied","client_credentials_denied","m2m_quota_exceeded","signup_disabled","email_taken","username_taken","invalid_username","invalid_email","invalid_name","same_email","user_has_no_email","password_too_weak","invalid_password_hash","weak_password_hash","invalid_code","invalid_phone","phone_changed","phone_already_verified","sms_unavailable","sms_failed","invalid_state","state_mismatch","invalid_ticket","ticket_used","ticket_expired","ticket_not_found","credential_not_found","invalid_link","expired_link","invalid_otp","invalid_client","client_not_found","client_mismatch","invalid_client_metadata","invalid_connection","no_database_connection","connection_required","connection_misconfigured","passwordless_unavailable","ambiguous_connection","invalid_redirect_uri","invalid_saml_request","saml_sp_mismatch","saml_not_enabled","invalid_saml_sp","origin_not_allowed","audience_not_found","system_resource","invalid_request","invalid_device_code","provider_error","identity_already_linked","identity_link_denied","identity_conflict","identity_orphaned","no_provider_token","no_refresh_token","action_unavailable","login_flow_misconfigured","mfa_required","step_up_required","reauth_required","invalid_current_password","email_not_verified","invalid_profile_field","interaction_required","passkey_login_disabled","mfa_pending","mfa_invalid_code","mfa_invalid_recovery_code","too_many_attempts","too_many_login_attempts","totp_not_allowed","no_usable_factor","factor_not_found","factor_already_confirmed","invalid_factor","session_missing","passkey_verification_failed","no_passkey_enrolled","malformed_credential","unknown_passkey","invalid_passkey","invalid_invite_url","invite_email_mismatch","invite_email_unverified","invite_not_found","invite_used","already_member","not_a_member","consent_options_unavailable","consent_choice_invalid","oauth21_required","registration_disabled","invalid_email_domain","public_email_domain","organization_domain_not_found","organization_domain_taken","organization_domain_unverified","plan_unavailable","organization_domain_mismatch","team_not_in_organization","organization_has_no_idp","organization_not_found","invalid_user","not_an_organization_admin","last_organization_admin","team_id_unavailable","use_team_member_route","role_not_found","invalid_role","invalid_permission","flow_not_found","revision_not_found","invalid_webhook_event","invalid_webhook_url","not_a_webhook","delivery_not_found","delivery_in_progress","account_mismatch","connect_redirect_uri_missing","invalid_site_url","handoff_not_found","handoff_expired","handoff_used","invalid_code_verifier"]},"ErrorResponse":{"type":"object","description":"Every error response has this shape. Branch on `error_code`, never on `message`: the message is written for a person and may change.","required":["status","message","error_code","docs"],"properties":{"status":{"type":"integer","description":"The HTTP status, repeated."},"message":{"type":"string","description":"Human-readable explanation. May change between releases."},"error_code":{"$ref":"#/components/schemas/ErrorCode","description":"Stable machine-readable code. Some codes carry a detail suffix after a colon (`sms_unavailable:plan`); branch on the prefix."},"docs":{"type":"string","format":"uri","description":"Link to the documentation of this error."},"details":{"type":"object","additionalProperties":true,"description":"Optional structured extras. `validation_error` and `already_exists` carry `fields`; `validation_error` also carries `issues`."}}}}},"paths":{"/webhooks/postmark":{"post":{"responses":{"200":{"description":"Default Response"},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/account/current":{"get":{"operationId":"account/current","summary":"Get current Account","tags":["account"],"description":"Returns the Account resolved from request context (header, hostname, or team id). Use `expand` to populate nested references.","parameters":[{"schema":{"type":"array","items":{"type":"string","maxLength":1000}},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `expand=user&expand=team`)."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"AuthAccount","content":{"application/json":{"schema":{"type":"object","required":["id","name","domain","slug","callback_hostnames","token_signature","token_signing_alg","enabled_locales","notification_settings","createdAt"],"properties":{"id":{"type":"string","description":"AuthAccount ID"},"name":{"type":"string"},"domain":{"type":"string"},"slug":{"type":"string"},"logo_src":{"type":"string","nullable":true},"icon_src":{"type":"string","nullable":true},"callback_hostnames":{"type":"array","items":{"type":"string"}},"token_signature":{"type":"string","description":"Shared HMAC secret. Used when token_signing_alg is an HS* algorithm; ignored for RS*/ES*/PS*."},"token_signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign tokens issued by this Account. Today only RS256 is implemented."},"default_connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"enabled_locales":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"},"description":"Free-form labels on the account. The dashboard stores the environment as an `env:<name>` tag (e.g. `env:production`, `env:staging`, `env:test`)."},"team":{"type":"string","nullable":true},"notification_settings":{"type":"object","required":["welcome_email_enabled","verify_email_auto_send"],"properties":{"welcome_email_enabled":{"type":"boolean","description":"Send the built-in welcome email on user.created","default":false},"verify_email_auto_send":{"type":"boolean","description":"Send a verification email automatically when a user is created with `email_verified=false` and an email address. The link in the email lands on `GET /verify-email?ticket=...` and flips `email_verified=true` with `email_verified_method=verification_flow`. When `false`, verification emails must be requested explicitly via `POST /user/:id/verify-email/start`.","default":false}},"additionalProperties":false},"email_reply_to":{"type":"string","format":"email","maxLength":320,"description":"Reply-To on every email this tenant sends to its users. Absent = no Reply-To, so a reply goes to the From address — which is a `no-reply@` mailbox nobody reads. Set it to your own support address if you want your users to be able to answer.","nullable":true},"email_from_name":{"type":"string","maxLength":100,"description":"Display name on every email this tenant sends to its users. Absent = the account `name`.","nullable":true},"email_from_address":{"type":"string","description":"Sender address of every email this tenant sends to its users. Absent = the platform default. Set by Faable only.","nullable":true},"welcome_email":{"type":"object","properties":{"cta_url":{"type":"string","format":"uri","maxLength":500,"description":"Where the welcome button sends the user. Defaults to `https://<first callback hostname>`. Point it at your app (not your marketing site) so a fresh user lands somewhere useful.","nullable":true},"body":{"type":"string","maxLength":600,"description":"Replaces the default one-line body (\"Your account on … is ready to go.\"). Plain text, sent verbatim in every enabled locale.","nullable":true},"social_links":{"type":"array","items":{"type":"object","required":["label","url"],"properties":{"label":{"type":"string","minLength":1,"maxLength":40},"url":{"type":"string","format":"uri","maxLength":500}},"additionalProperties":false},"maxItems":6,"description":"Rendered as a \"Follow <account>\" block after the button — GitHub, LinkedIn, X, YouTube… Empty or absent hides the block.","nullable":true}},"additionalProperties":false,"nullable":true},"email_change_verification_mode":{"anyOf":[{"type":"string","enum":["new_only"]},{"type":"string","enum":["old_and_new"]}],"description":"Policy for the user email-change flow. `new_only` (default) sends a single confirmation link to the new email. `old_and_new` requires the user to also click a link sent to the previous email before the swap takes effect — stricter, useful for tenants with higher-risk users."},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]}],"description":"Controls what happens to `user.email` on subsequent OAuth/federated logins when the user previously changed their email manually through this auth server (i.e. `user.email_change_locked_at` is set).\n\n- `preserve_manual` (default): the manually-set email wins. The federated provider's email is ignored on re-sync; `email` and `email_verified` are not touched. The identity link stays valid via `provider_user_id`, so the user can still log in with Google/etc.\n- `always_sync`: the federated provider's email is always written back, overwriting any manual change. Useful for tenants whose source of truth for identity is the IdP (corporate SSO, etc.)."},"login_methods":{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"login_flow":{"type":"string","description":"The login flow bound to this account (`loginflow_xxx`). Absent = the flow compiled from the settings. A Client may bind its own.","nullable":true},"mfa_policy":{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},"recovery_channels":{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},"default_country_iso":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country assumed for phone numbers written without an international prefix (`636647460` → `+34…` with `ES`). Needed before SMS can reach anyone: most people type their number without a prefix.","nullable":true},"webauthn_rp_id":{"type":"string","description":"WebAuthn Relying Party ID for this tenant's passkeys. Defaults to the account domain. Set it to a registrable suffix you own (e.g. `acme.com`) when the login screen is served from more than one host — the RP ID is frozen into every credential at registration, so changing it afterwards invalidates every passkey already enrolled.","nullable":true},"logout_confirm_required":{"type":"boolean","description":"When true, `/logout` asks the End-User to confirm before ending the session unless the request carries a verified `id_token_hint`. Off by default — a plain link to `/logout` is otherwise enough to sign anyone out (logout CSRF)."},"dynamic_client_registration_enabled":{"type":"boolean","description":"When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`."},"oauth21_enabled":{"type":"boolean","description":"When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later."},"oauth_link_verified_email":{"type":"boolean","description":"When true, signing in with Google, GitHub, GitLab or Apple for the first time attaches that provider to the existing user with the same email instead of creating a second user — only when the provider attests the email as verified, exactly one user of the tenant has that email verified, that user is not suspended and has no other identity of the same connection. Off by default."},"client_id_metadata_documents_enabled":{"type":"boolean","description":"When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default."},"canonical_host":{"type":"string","description":"The one host this tenant's OIDC issuer and discovery document are always minted with, regardless of which host actually served the request — must be the account's own platform host or one of its verified custom domains. `/authorize` and `/logout` 302 here from any other host of the tenant. Unset (the default): every URL uses whatever host served the request, as before this field existed. Changing it can invalidate every WebAuthn passkey already enrolled unless `webauthn_rp_id` already covers the new host.","nullable":true},"createdAt":{"type":"string","description":"AuthAccount creation date"},"updatedAt":{"type":"string","description":"AuthAccount updated date"}},"description":"AuthAccount"}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/account":{"post":{"operationId":"account/create","summary":"Create an Account","tags":["account"],"description":"Creates a new Auth Account. The slug is derived from the name and used to build the default `*.auth.faable.link` domain.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","team"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100},"team":{"type":"string"},"logo_src":{"type":"string"},"icon_src":{"type":"string"},"callback_hostnames":{"type":"array","items":{"type":"string"}},"default_connection":{"type":"string"},"tags":{"type":"array","items":{"type":"string","maxLength":50},"maxItems":20},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"AuthAccountCreate","additionalProperties":false}}},"description":"AuthAccountCreate"},"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"AuthAccount","content":{"application/json":{"schema":{"type":"object","required":["id","name","domain","slug","callback_hostnames","token_signature","token_signing_alg","enabled_locales","notification_settings","createdAt"],"properties":{"id":{"type":"string","description":"AuthAccount ID"},"name":{"type":"string"},"domain":{"type":"string"},"slug":{"type":"string"},"logo_src":{"type":"string","nullable":true},"icon_src":{"type":"string","nullable":true},"callback_hostnames":{"type":"array","items":{"type":"string"}},"token_signature":{"type":"string","description":"Shared HMAC secret. Used when token_signing_alg is an HS* algorithm; ignored for RS*/ES*/PS*."},"token_signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign tokens issued by this Account. Today only RS256 is implemented."},"default_connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"enabled_locales":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"},"description":"Free-form labels on the account. The dashboard stores the environment as an `env:<name>` tag (e.g. `env:production`, `env:staging`, `env:test`)."},"team":{"type":"string","nullable":true},"notification_settings":{"type":"object","required":["welcome_email_enabled","verify_email_auto_send"],"properties":{"welcome_email_enabled":{"type":"boolean","description":"Send the built-in welcome email on user.created","default":false},"verify_email_auto_send":{"type":"boolean","description":"Send a verification email automatically when a user is created with `email_verified=false` and an email address. The link in the email lands on `GET /verify-email?ticket=...` and flips `email_verified=true` with `email_verified_method=verification_flow`. When `false`, verification emails must be requested explicitly via `POST /user/:id/verify-email/start`.","default":false}},"additionalProperties":false},"email_reply_to":{"type":"string","format":"email","maxLength":320,"description":"Reply-To on every email this tenant sends to its users. Absent = no Reply-To, so a reply goes to the From address — which is a `no-reply@` mailbox nobody reads. Set it to your own support address if you want your users to be able to answer.","nullable":true},"email_from_name":{"type":"string","maxLength":100,"description":"Display name on every email this tenant sends to its users. Absent = the account `name`.","nullable":true},"email_from_address":{"type":"string","description":"Sender address of every email this tenant sends to its users. Absent = the platform default. Set by Faable only.","nullable":true},"welcome_email":{"type":"object","properties":{"cta_url":{"type":"string","format":"uri","maxLength":500,"description":"Where the welcome button sends the user. Defaults to `https://<first callback hostname>`. Point it at your app (not your marketing site) so a fresh user lands somewhere useful.","nullable":true},"body":{"type":"string","maxLength":600,"description":"Replaces the default one-line body (\"Your account on … is ready to go.\"). Plain text, sent verbatim in every enabled locale.","nullable":true},"social_links":{"type":"array","items":{"type":"object","required":["label","url"],"properties":{"label":{"type":"string","minLength":1,"maxLength":40},"url":{"type":"string","format":"uri","maxLength":500}},"additionalProperties":false},"maxItems":6,"description":"Rendered as a \"Follow <account>\" block after the button — GitHub, LinkedIn, X, YouTube… Empty or absent hides the block.","nullable":true}},"additionalProperties":false,"nullable":true},"email_change_verification_mode":{"anyOf":[{"type":"string","enum":["new_only"]},{"type":"string","enum":["old_and_new"]}],"description":"Policy for the user email-change flow. `new_only` (default) sends a single confirmation link to the new email. `old_and_new` requires the user to also click a link sent to the previous email before the swap takes effect — stricter, useful for tenants with higher-risk users."},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]}],"description":"Controls what happens to `user.email` on subsequent OAuth/federated logins when the user previously changed their email manually through this auth server (i.e. `user.email_change_locked_at` is set).\n\n- `preserve_manual` (default): the manually-set email wins. The federated provider's email is ignored on re-sync; `email` and `email_verified` are not touched. The identity link stays valid via `provider_user_id`, so the user can still log in with Google/etc.\n- `always_sync`: the federated provider's email is always written back, overwriting any manual change. Useful for tenants whose source of truth for identity is the IdP (corporate SSO, etc.)."},"login_methods":{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"login_flow":{"type":"string","description":"The login flow bound to this account (`loginflow_xxx`). Absent = the flow compiled from the settings. A Client may bind its own.","nullable":true},"mfa_policy":{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},"recovery_channels":{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},"default_country_iso":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country assumed for phone numbers written without an international prefix (`636647460` → `+34…` with `ES`). Needed before SMS can reach anyone: most people type their number without a prefix.","nullable":true},"webauthn_rp_id":{"type":"string","description":"WebAuthn Relying Party ID for this tenant's passkeys. Defaults to the account domain. Set it to a registrable suffix you own (e.g. `acme.com`) when the login screen is served from more than one host — the RP ID is frozen into every credential at registration, so changing it afterwards invalidates every passkey already enrolled.","nullable":true},"logout_confirm_required":{"type":"boolean","description":"When true, `/logout` asks the End-User to confirm before ending the session unless the request carries a verified `id_token_hint`. Off by default — a plain link to `/logout` is otherwise enough to sign anyone out (logout CSRF)."},"dynamic_client_registration_enabled":{"type":"boolean","description":"When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`."},"oauth21_enabled":{"type":"boolean","description":"When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later."},"oauth_link_verified_email":{"type":"boolean","description":"When true, signing in with Google, GitHub, GitLab or Apple for the first time attaches that provider to the existing user with the same email instead of creating a second user — only when the provider attests the email as verified, exactly one user of the tenant has that email verified, that user is not suspended and has no other identity of the same connection. Off by default."},"client_id_metadata_documents_enabled":{"type":"boolean","description":"When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default."},"canonical_host":{"type":"string","description":"The one host this tenant's OIDC issuer and discovery document are always minted with, regardless of which host actually served the request — must be the account's own platform host or one of its verified custom domains. `/authorize` and `/logout` 302 here from any other host of the tenant. Unset (the default): every URL uses whatever host served the request, as before this field existed. Changing it can invalidate every WebAuthn passkey already enrolled unless `webauthn_rp_id` already covers the new host.","nullable":true},"createdAt":{"type":"string","description":"AuthAccount creation date"},"updatedAt":{"type":"string","description":"AuthAccount updated date"}},"description":"AuthAccount"}}}},"400":{"description":"`team_required` — An Auth Account must belong to a project (team).\n\n`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.\n\n`invalid_name` — The name contains a link. `message` says which field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["team_required","already_exists","validation_error","invalid_name"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"409":{"description":"`account_limit_reached` — The project already has the maximum number of Auth Accounts.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_limit_reached"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"get":{"operationId":"account/list","summary":"List Accounts","tags":["account"],"description":"List Accounts owned by the requesting project/team. Supports cursor pagination and `expand`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Start from this cursor"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Start from this cursor"},{"schema":{"type":"number"},"in":"query","name":"pageSize","required":false,"description":"Size of the results array"},{"schema":{"type":"array","items":{"type":"string","maxLength":1000}},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `expand=user&expand=team`)."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"PaginatedResponse","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"type":"string","description":"next cursor","nullable":true},"results":{"type":"array","items":{"$ref":"#/components/schemas/AuthAccount"},"description":"List of results"}},"description":"PaginatedResponse","additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/account/{account_id}":{"get":{"operationId":"account/get","summary":"Get an Account","tags":["account"],"description":"Fetch a single Account by its id. Use `expand` to populate id-only fields (e.g. `default_connection`) inline.","parameters":[{"schema":{"type":"array","items":{"type":"string","maxLength":1000}},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `expand=user&expand=team`)."},{"schema":{"type":"string"},"in":"path","name":"account_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"AuthAccount","content":{"application/json":{"schema":{"type":"object","required":["id","name","domain","slug","callback_hostnames","token_signature","token_signing_alg","enabled_locales","notification_settings","createdAt"],"properties":{"id":{"type":"string","description":"AuthAccount ID"},"name":{"type":"string"},"domain":{"type":"string"},"slug":{"type":"string"},"logo_src":{"type":"string","nullable":true},"icon_src":{"type":"string","nullable":true},"callback_hostnames":{"type":"array","items":{"type":"string"}},"token_signature":{"type":"string","description":"Shared HMAC secret. Used when token_signing_alg is an HS* algorithm; ignored for RS*/ES*/PS*."},"token_signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign tokens issued by this Account. Today only RS256 is implemented."},"default_connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"enabled_locales":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"},"description":"Free-form labels on the account. The dashboard stores the environment as an `env:<name>` tag (e.g. `env:production`, `env:staging`, `env:test`)."},"team":{"type":"string","nullable":true},"notification_settings":{"type":"object","required":["welcome_email_enabled","verify_email_auto_send"],"properties":{"welcome_email_enabled":{"type":"boolean","description":"Send the built-in welcome email on user.created","default":false},"verify_email_auto_send":{"type":"boolean","description":"Send a verification email automatically when a user is created with `email_verified=false` and an email address. The link in the email lands on `GET /verify-email?ticket=...` and flips `email_verified=true` with `email_verified_method=verification_flow`. When `false`, verification emails must be requested explicitly via `POST /user/:id/verify-email/start`.","default":false}},"additionalProperties":false},"email_reply_to":{"type":"string","format":"email","maxLength":320,"description":"Reply-To on every email this tenant sends to its users. Absent = no Reply-To, so a reply goes to the From address — which is a `no-reply@` mailbox nobody reads. Set it to your own support address if you want your users to be able to answer.","nullable":true},"email_from_name":{"type":"string","maxLength":100,"description":"Display name on every email this tenant sends to its users. Absent = the account `name`.","nullable":true},"email_from_address":{"type":"string","description":"Sender address of every email this tenant sends to its users. Absent = the platform default. Set by Faable only.","nullable":true},"welcome_email":{"type":"object","properties":{"cta_url":{"type":"string","format":"uri","maxLength":500,"description":"Where the welcome button sends the user. Defaults to `https://<first callback hostname>`. Point it at your app (not your marketing site) so a fresh user lands somewhere useful.","nullable":true},"body":{"type":"string","maxLength":600,"description":"Replaces the default one-line body (\"Your account on … is ready to go.\"). Plain text, sent verbatim in every enabled locale.","nullable":true},"social_links":{"type":"array","items":{"type":"object","required":["label","url"],"properties":{"label":{"type":"string","minLength":1,"maxLength":40},"url":{"type":"string","format":"uri","maxLength":500}},"additionalProperties":false},"maxItems":6,"description":"Rendered as a \"Follow <account>\" block after the button — GitHub, LinkedIn, X, YouTube… Empty or absent hides the block.","nullable":true}},"additionalProperties":false,"nullable":true},"email_change_verification_mode":{"anyOf":[{"type":"string","enum":["new_only"]},{"type":"string","enum":["old_and_new"]}],"description":"Policy for the user email-change flow. `new_only` (default) sends a single confirmation link to the new email. `old_and_new` requires the user to also click a link sent to the previous email before the swap takes effect — stricter, useful for tenants with higher-risk users."},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]}],"description":"Controls what happens to `user.email` on subsequent OAuth/federated logins when the user previously changed their email manually through this auth server (i.e. `user.email_change_locked_at` is set).\n\n- `preserve_manual` (default): the manually-set email wins. The federated provider's email is ignored on re-sync; `email` and `email_verified` are not touched. The identity link stays valid via `provider_user_id`, so the user can still log in with Google/etc.\n- `always_sync`: the federated provider's email is always written back, overwriting any manual change. Useful for tenants whose source of truth for identity is the IdP (corporate SSO, etc.)."},"login_methods":{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"login_flow":{"type":"string","description":"The login flow bound to this account (`loginflow_xxx`). Absent = the flow compiled from the settings. A Client may bind its own.","nullable":true},"mfa_policy":{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},"recovery_channels":{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},"default_country_iso":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country assumed for phone numbers written without an international prefix (`636647460` → `+34…` with `ES`). Needed before SMS can reach anyone: most people type their number without a prefix.","nullable":true},"webauthn_rp_id":{"type":"string","description":"WebAuthn Relying Party ID for this tenant's passkeys. Defaults to the account domain. Set it to a registrable suffix you own (e.g. `acme.com`) when the login screen is served from more than one host — the RP ID is frozen into every credential at registration, so changing it afterwards invalidates every passkey already enrolled.","nullable":true},"logout_confirm_required":{"type":"boolean","description":"When true, `/logout` asks the End-User to confirm before ending the session unless the request carries a verified `id_token_hint`. Off by default — a plain link to `/logout` is otherwise enough to sign anyone out (logout CSRF)."},"dynamic_client_registration_enabled":{"type":"boolean","description":"When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`."},"oauth21_enabled":{"type":"boolean","description":"When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later."},"oauth_link_verified_email":{"type":"boolean","description":"When true, signing in with Google, GitHub, GitLab or Apple for the first time attaches that provider to the existing user with the same email instead of creating a second user — only when the provider attests the email as verified, exactly one user of the tenant has that email verified, that user is not suspended and has no other identity of the same connection. Off by default."},"client_id_metadata_documents_enabled":{"type":"boolean","description":"When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default."},"canonical_host":{"type":"string","description":"The one host this tenant's OIDC issuer and discovery document are always minted with, regardless of which host actually served the request — must be the account's own platform host or one of its verified custom domains. `/authorize` and `/logout` 302 here from any other host of the tenant. Unset (the default): every URL uses whatever host served the request, as before this field existed. Changing it can invalidate every WebAuthn passkey already enrolled unless `webauthn_rp_id` already covers the new host.","nullable":true},"createdAt":{"type":"string","description":"AuthAccount creation date"},"updatedAt":{"type":"string","description":"AuthAccount updated date"}},"description":"AuthAccount"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"account/update","summary":"Update an Account","tags":["account"],"description":"Update the Account configuration such as branding (logo and icon URLs).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"logo_src":{"type":"string","nullable":true},"icon_src":{"type":"string","nullable":true},"callback_hostnames":{"type":"array","items":{"type":"string"}},"default_connection":{"type":"string","nullable":true},"enabled_locales":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string","maxLength":50},"maxItems":20},"notification_settings":{"type":"object","properties":{"welcome_email_enabled":{"type":"boolean","description":"Send the built-in welcome email on user.created","default":false},"verify_email_auto_send":{"type":"boolean","description":"Send a verification email automatically when a user is created with `email_verified=false` and an email address. The link in the email lands on `GET /verify-email?ticket=...` and flips `email_verified=true` with `email_verified_method=verification_flow`. When `false`, verification emails must be requested explicitly via `POST /user/:id/verify-email/start`.","default":false}}},"email_reply_to":{"type":"string","format":"email","maxLength":320,"nullable":true},"email_from_name":{"type":"string","minLength":1,"maxLength":100,"nullable":true},"welcome_email":{"anyOf":[{"type":"object","properties":{"cta_url":{"type":"string","format":"uri","maxLength":500,"description":"Where the welcome button sends the user. Defaults to `https://<first callback hostname>`. Point it at your app (not your marketing site) so a fresh user lands somewhere useful.","nullable":true},"body":{"type":"string","maxLength":600,"description":"Replaces the default one-line body (\"Your account on … is ready to go.\"). Plain text, sent verbatim in every enabled locale.","nullable":true},"social_links":{"type":"array","items":{"type":"object","required":["label","url"],"properties":{"label":{"type":"string","minLength":1,"maxLength":40},"url":{"type":"string","format":"uri","maxLength":500}},"additionalProperties":false},"maxItems":6,"description":"Rendered as a \"Follow <account>\" block after the button — GitHub, LinkedIn, X, YouTube… Empty or absent hides the block.","nullable":true}},"additionalProperties":false},{"type":"null"}]},"login_methods":{"anyOf":[{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},{"type":"null"}]},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"mfa_policy":{"anyOf":[{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},{"type":"null"}]},"recovery_channels":{"anyOf":[{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},{"type":"null"}]},"default_country_iso":{"anyOf":[{"type":"string","minLength":2,"maxLength":2},{"type":"null"}]},"webauthn_rp_id":{"type":"string","nullable":true},"logout_confirm_required":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"dynamic_client_registration_enabled":{"type":"boolean","nullable":true},"client_id_metadata_documents_enabled":{"type":"boolean","nullable":true},"oauth21_enabled":{"type":"boolean","nullable":true},"oauth_link_verified_email":{"type":"boolean","nullable":true},"canonical_host":{"type":"string","nullable":true},"login_flow":{"type":"string","nullable":true}},"description":"AuthAccountUpdate","additionalProperties":false}}},"description":"AuthAccountUpdate"},"parameters":[{"schema":{"type":"string"},"in":"path","name":"account_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"AuthAccount","content":{"application/json":{"schema":{"type":"object","required":["id","name","domain","slug","callback_hostnames","token_signature","token_signing_alg","enabled_locales","notification_settings","createdAt"],"properties":{"id":{"type":"string","description":"AuthAccount ID"},"name":{"type":"string"},"domain":{"type":"string"},"slug":{"type":"string"},"logo_src":{"type":"string","nullable":true},"icon_src":{"type":"string","nullable":true},"callback_hostnames":{"type":"array","items":{"type":"string"}},"token_signature":{"type":"string","description":"Shared HMAC secret. Used when token_signing_alg is an HS* algorithm; ignored for RS*/ES*/PS*."},"token_signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign tokens issued by this Account. Today only RS256 is implemented."},"default_connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"enabled_locales":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"},"description":"Free-form labels on the account. The dashboard stores the environment as an `env:<name>` tag (e.g. `env:production`, `env:staging`, `env:test`)."},"team":{"type":"string","nullable":true},"notification_settings":{"type":"object","required":["welcome_email_enabled","verify_email_auto_send"],"properties":{"welcome_email_enabled":{"type":"boolean","description":"Send the built-in welcome email on user.created","default":false},"verify_email_auto_send":{"type":"boolean","description":"Send a verification email automatically when a user is created with `email_verified=false` and an email address. The link in the email lands on `GET /verify-email?ticket=...` and flips `email_verified=true` with `email_verified_method=verification_flow`. When `false`, verification emails must be requested explicitly via `POST /user/:id/verify-email/start`.","default":false}},"additionalProperties":false},"email_reply_to":{"type":"string","format":"email","maxLength":320,"description":"Reply-To on every email this tenant sends to its users. Absent = no Reply-To, so a reply goes to the From address — which is a `no-reply@` mailbox nobody reads. Set it to your own support address if you want your users to be able to answer.","nullable":true},"email_from_name":{"type":"string","maxLength":100,"description":"Display name on every email this tenant sends to its users. Absent = the account `name`.","nullable":true},"email_from_address":{"type":"string","description":"Sender address of every email this tenant sends to its users. Absent = the platform default. Set by Faable only.","nullable":true},"welcome_email":{"type":"object","properties":{"cta_url":{"type":"string","format":"uri","maxLength":500,"description":"Where the welcome button sends the user. Defaults to `https://<first callback hostname>`. Point it at your app (not your marketing site) so a fresh user lands somewhere useful.","nullable":true},"body":{"type":"string","maxLength":600,"description":"Replaces the default one-line body (\"Your account on … is ready to go.\"). Plain text, sent verbatim in every enabled locale.","nullable":true},"social_links":{"type":"array","items":{"type":"object","required":["label","url"],"properties":{"label":{"type":"string","minLength":1,"maxLength":40},"url":{"type":"string","format":"uri","maxLength":500}},"additionalProperties":false},"maxItems":6,"description":"Rendered as a \"Follow <account>\" block after the button — GitHub, LinkedIn, X, YouTube… Empty or absent hides the block.","nullable":true}},"additionalProperties":false,"nullable":true},"email_change_verification_mode":{"anyOf":[{"type":"string","enum":["new_only"]},{"type":"string","enum":["old_and_new"]}],"description":"Policy for the user email-change flow. `new_only` (default) sends a single confirmation link to the new email. `old_and_new` requires the user to also click a link sent to the previous email before the swap takes effect — stricter, useful for tenants with higher-risk users."},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]}],"description":"Controls what happens to `user.email` on subsequent OAuth/federated logins when the user previously changed their email manually through this auth server (i.e. `user.email_change_locked_at` is set).\n\n- `preserve_manual` (default): the manually-set email wins. The federated provider's email is ignored on re-sync; `email` and `email_verified` are not touched. The identity link stays valid via `provider_user_id`, so the user can still log in with Google/etc.\n- `always_sync`: the federated provider's email is always written back, overwriting any manual change. Useful for tenants whose source of truth for identity is the IdP (corporate SSO, etc.)."},"login_methods":{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"login_flow":{"type":"string","description":"The login flow bound to this account (`loginflow_xxx`). Absent = the flow compiled from the settings. A Client may bind its own.","nullable":true},"mfa_policy":{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},"recovery_channels":{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},"default_country_iso":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country assumed for phone numbers written without an international prefix (`636647460` → `+34…` with `ES`). Needed before SMS can reach anyone: most people type their number without a prefix.","nullable":true},"webauthn_rp_id":{"type":"string","description":"WebAuthn Relying Party ID for this tenant's passkeys. Defaults to the account domain. Set it to a registrable suffix you own (e.g. `acme.com`) when the login screen is served from more than one host — the RP ID is frozen into every credential at registration, so changing it afterwards invalidates every passkey already enrolled.","nullable":true},"logout_confirm_required":{"type":"boolean","description":"When true, `/logout` asks the End-User to confirm before ending the session unless the request carries a verified `id_token_hint`. Off by default — a plain link to `/logout` is otherwise enough to sign anyone out (logout CSRF)."},"dynamic_client_registration_enabled":{"type":"boolean","description":"When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`."},"oauth21_enabled":{"type":"boolean","description":"When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later."},"oauth_link_verified_email":{"type":"boolean","description":"When true, signing in with Google, GitHub, GitLab or Apple for the first time attaches that provider to the existing user with the same email instead of creating a second user — only when the provider attests the email as verified, exactly one user of the tenant has that email verified, that user is not suspended and has no other identity of the same connection. Off by default."},"client_id_metadata_documents_enabled":{"type":"boolean","description":"When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default."},"canonical_host":{"type":"string","description":"The one host this tenant's OIDC issuer and discovery document are always minted with, regardless of which host actually served the request — must be the account's own platform host or one of its verified custom domains. `/authorize` and `/logout` 302 here from any other host of the tenant. Unset (the default): every URL uses whatever host served the request, as before this field existed. Changing it can invalidate every WebAuthn passkey already enrolled unless `webauthn_rp_id` already covers the new host.","nullable":true},"createdAt":{"type":"string","description":"AuthAccount creation date"},"updatedAt":{"type":"string","description":"AuthAccount updated date"}},"description":"AuthAccount"}}}},"400":{"description":"`invalid_canonical_host` — `canonical_host` must be this account's own platform host or one of its verified custom domains.\n\n`canonical_host_breaks_passkeys` — `canonical_host` is outside the account's current WebAuthn RP ID — every passkey already enrolled would stop working. Set `webauthn_rp_id` in the same request to a suffix that covers both.\n\n`invalid_id` — The id in the path is not a valid id for this resource.\n\n`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.\n\n`invalid_name` — The name contains a link. `message` says which field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_canonical_host","canonical_host_breaks_passkeys","invalid_id","already_exists","validation_error","invalid_name"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"402":{"description":"`plan_required` — The setting requires a higher plan. `message` says which.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["plan_required"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"account/delete","summary":"Delete an Account","tags":["account"],"description":"Permanently removes an Account and its associated data. This action cannot be undone.","parameters":[{"schema":{"type":"string"},"in":"path","name":"account_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/account/host/{host}":{"get":{"operationId":"account/getByHost","summary":"Find Account by host","tags":["account"],"description":"Looks up the Account that owns the given hostname, checking both the built-in `*.auth.faable.link` slug and any verified custom domains. Returns only its public fields.","parameters":[{"schema":{"type":"string"},"in":"path","name":"host","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["id","name","domain","slug"],"properties":{"id":{"type":"string","description":"AuthAccount ID"},"name":{"type":"string"},"domain":{"type":"string"},"slug":{"type":"string"},"logo_src":{"type":"string","nullable":true},"icon_src":{"type":"string","nullable":true}},"additionalProperties":false}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/internal/mgmt-token":{"post":{"operationId":"internal/mintMgmtToken","summary":"Mint a tenant management token for a user (internal)","tags":["account"],"description":"Internal, service-to-service. Issues a short-lived Management API token bound to `account_id`, acting for the platform user `user_id`. Only the platform's own client_credentials clients listed in `MGMT_TOKEN_MINTER_CLIENT_IDS` may call it; the caller is responsible for checking that the user may manage that tenant. The target is taken from the body, never from `x-faableauth-account`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["account_id","user_id"],"properties":{"account_id":{"type":"string","pattern":"^account_[0-9a-f]{24}$","description":"The tenant the token will manage."},"user_id":{"type":"string","pattern":"^user_[0-9a-f]{24}$","description":"The platform (root) user the caller acts for. The caller has already checked that this user may manage the tenant."},"ttl":{"type":"integer","minimum":60,"maximum":900,"default":900,"description":"Lifetime in seconds."},"scopes":{"type":"array","items":{"type":"string"},"minItems":1,"uniqueItems":true,"description":"Narrow the token to these management scopes (each must be in the catalog). Omitted: the whole catalog."},"apikey_id":{"type":"string","minLength":1,"maxLength":64,"description":"Set when the user acts through one of their deploy API keys (the hosted MCP). Recorded in the audit log and as `via_apikey` in the token."}},"additionalProperties":false}}}},"security":[{"bearerAuth":[]}],"x-internal":true,"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["access_token","token_type","expires_in"],"properties":{"access_token":{"type":"string"},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","user_suspended","insufficient_scope"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).\n\n`not_found` — The resource does not exist in this account.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found","not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/recovery-channels/capabilities":{"get":{"operationId":"account/recoveryChannelsCapabilities","summary":"Recovery channel capabilities for this tenant","tags":["account"],"description":"Which recovery channels the platform can send, whether the plan allows the paid ones, this month's SMS usage against the included amount, and the tenant configuration as resolved. Drives the \"Recovery channels\" editor in the dashboard.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["provider","plan","usage","resolved","channels"],"properties":{"provider":{"type":"object","required":["sms","whatsapp"],"properties":{"sms":{"type":"boolean"},"whatsapp":{"type":"boolean"}},"description":"Whether the platform can send on each channel right now."},"plan":{"type":"object","required":["known","allows_messaging"],"properties":{"known":{"type":"boolean","description":"False when billing did not answer. Messaging channels are then treated as not allowed — the failure mode for a paid-per-message channel is \"do not send\", not \"allow\"."},"tier":{"type":"string"},"allows_messaging":{"type":"boolean"}}},"usage":{"type":"object","required":["month","sent","included","metered","hard_ceiling"],"properties":{"month":{"type":"string","description":"YYYY-MM, UTC."},"sent":{"type":"integer"},"included":{"type":"integer"},"metered":{"type":"boolean"},"hard_ceiling":{"type":"integer"}}},"resolved":{"type":"object","required":["enabled","default","visible"],"properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}},"default":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}}}},"channels":{"type":"array","items":{"type":"object","required":["channel","available"],"properties":{"channel":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"available":{"type":"boolean"},"reason":{"type":"string"}}},"description":"Tenant-level availability (configuration × platform × plan). The per-user step — verified phone, enrolled factor — is not applied here."}}}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/internal/usage/sms":{"get":{"operationId":"internal/smsUsage","summary":"SMS sent per tenant in a month (platform)","tags":["account"],"description":"Per-account count of recovery/verification SMS sent in the given month, read from the monthly quota counters. Consumed by the billing usage job. Platform token with `read:usage`.","parameters":[{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"in":"query","name":"month","required":true,"description":"YYYY-MM, UTC. Defaults to the current month."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["month","rows"],"properties":{"month":{"type":"string"},"rows":{"type":"array","items":{"type":"object","required":["account_id","team","sent"],"properties":{"account_id":{"type":"string"},"team":{"anyOf":[{"type":"string"},{"type":"null"}]},"sent":{"type":"integer"}}}}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/internal/usage/sms/range":{"get":{"operationId":"internal/smsUsageRange","summary":"SMS a project sent in a time range (platform)","tags":["account"],"description":"Sum of the hourly counters of SMS and WhatsApp messages one project handed to the provider between `from` (first full hour after it) and `to`. At most 40 days back. Platform token with `read:usage`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"team","required":true,"description":"The project to read, as `team_<hex>`."},{"schema":{"type":"string","format":"date-time"},"in":"query","name":"from","required":true,"description":"Start of the range — the subscription period start. Counted from the first full hour after it."},{"schema":{"type":"string","format":"date-time"},"in":"query","name":"to","required":false,"description":"End of the range. Defaults to now."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["team","from","to","sent"],"properties":{"team":{"type":"string"},"from":{"type":"string"},"to":{"type":"string"},"sent":{"type":"integer"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/internal/usage/m2m":{"get":{"operationId":"internal/m2mUsage","summary":"M2M tokens served per project in a month (platform)","tags":["account"],"description":"Per-project count of successful client_credentials token responses in the given month (cached ones included), read from the monthly counters. Consumed by the billing usage job. Platform token with `read:usage`.","parameters":[{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"in":"query","name":"month","required":true,"description":"YYYY-MM, UTC."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["month","rows"],"properties":{"month":{"type":"string"},"rows":{"type":"array","items":{"type":"object","required":["team","tokens"],"properties":{"team":{"type":"string","description":"The project (`team_<hex>`), or the account id for a legacy account without one."},"tokens":{"type":"integer"}}}}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/internal/usage/m2m/range":{"get":{"operationId":"internal/m2mUsageRange","summary":"M2M tokens a project was served in a time range (platform)","tags":["account"],"description":"Sum of the hourly client_credentials counters of one project between `from` (first full hour after it) and `to`. At most 40 days back. Platform token with `read:usage`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"team","required":true,"description":"The project to read, as `team_<hex>`."},{"schema":{"type":"string","format":"date-time"},"in":"query","name":"from","required":true,"description":"Start of the range — the subscription period start. Counted from the first full hour after it."},{"schema":{"type":"string","format":"date-time"},"in":"query","name":"to","required":false,"description":"End of the range. Defaults to now."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["team","from","to","tokens"],"properties":{"team":{"type":"string"},"from":{"type":"string"},"to":{"type":"string"},"tokens":{"type":"integer"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/internal/accounts":{"get":{"operationId":"internal/accountsOverview","summary":"Every tenant with its usage counters (platform)","tags":["account"],"description":"All accounts on the platform, each with its users, clients, connections, last login, first end-user login and whether it has a verified custom domain. Platform token with `read:accounts`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["rows"],"properties":{"rows":{"type":"array","items":{"type":"object","required":["account_id","team","name","createdAt","first_login_at","users","last_login","clients","connections","custom_domain_verified"],"properties":{"account_id":{"type":"string"},"team":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The project (`team_<hex>`), or null for a legacy account without one."},"name":{"type":"string"},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"first_login_at":{"anyOf":[{"type":"string","description":"When the first end user ever completed a login on this tenant — the integration signal (arch/auth/activation-signal.md)."},{"type":"null"}]},"users":{"type":"integer"},"last_login":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"clients":{"type":"integer"},"connections":{"type":"integer"},"custom_domain_verified":{"type":"boolean"}}}}}}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/account/{account_id}/transfer":{"post":{"operationId":"account/transfer","summary":"Move an Account to another project","tags":["account"],"description":"Re-parents an Auth Account (tenant) under another Faable project. Only `Account.team` changes: the tenant keeps its id, slug, `*.auth.faable.link` domain, clients and secrets, and end-user logins are unaffected. `dry_run` returns the plan (what rides along + blockers) without writing. Platform credentials only. See arch/auth/tenant-project-transfer.md.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to_project","reason"],"properties":{"to_project":{"type":"string","description":"Destination project (`project_<hex>`) or its team id (`team_<hex>`) — both spellings are accepted."},"reason":{"type":"string","minLength":3,"maxLength":500,"description":"Why the tenant is being moved. Recorded in the tenant's log and in ours."},"dry_run":{"type":"boolean","default":false,"description":"Return the plan (what rides along + blockers) without writing."},"allow_over_cap":{"type":"boolean","default":false,"description":"Move even if the destination ends up above the per-project cap on Auth Accounts."}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"account_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["moved","account_id","account_name","slug","domain","from_project","to_project","target_tenants_before","moves_with_it","blockers"],"properties":{"moved":{"type":"boolean"},"account_id":{"type":"string"},"account_name":{"type":"string"},"slug":{"type":"string"},"domain":{"type":"string"},"from_project":{"type":"string"},"to_project":{"type":"string"},"target_tenants_before":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"moves_with_it":{"type":"object","additionalProperties":{"type":"number"}},"blockers":{"type":"array","items":{"type":"object","required":["code","detail"],"properties":{"code":{"type":"string"},"detail":{"type":"string"}}}},"tenants_left_in_source":{"type":"number"},"tenants_in_target":{"type":"number"}}}}}},"400":{"description":"`invalid_project_id` — The project id is not of the form `project_<hex>` or `team_<hex>`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_project_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"409":{"description":"`account_transfer_refused` — The Auth Account cannot be moved to that project. `details.blockers` lists why.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_transfer_refused"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/account/{account_id}/email_sender":{"post":{"operationId":"account/email_sender","summary":"Set an Account's sender address","tags":["account"],"description":"Sets the From address of every email this Auth Account sends to its users. The address must be in a domain the platform signs email for. `null` returns to the platform default. Platform credentials only.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["address"],"properties":{"address":{"type":"string","format":"email","maxLength":320,"nullable":true}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"account_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"AuthAccount","content":{"application/json":{"schema":{"type":"object","required":["id","name","domain","slug","callback_hostnames","token_signature","token_signing_alg","enabled_locales","notification_settings","createdAt"],"properties":{"id":{"type":"string","description":"AuthAccount ID"},"name":{"type":"string"},"domain":{"type":"string"},"slug":{"type":"string"},"logo_src":{"type":"string","nullable":true},"icon_src":{"type":"string","nullable":true},"callback_hostnames":{"type":"array","items":{"type":"string"}},"token_signature":{"type":"string","description":"Shared HMAC secret. Used when token_signing_alg is an HS* algorithm; ignored for RS*/ES*/PS*."},"token_signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign tokens issued by this Account. Today only RS256 is implemented."},"default_connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"enabled_locales":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"},"description":"Free-form labels on the account. The dashboard stores the environment as an `env:<name>` tag (e.g. `env:production`, `env:staging`, `env:test`)."},"team":{"type":"string","nullable":true},"notification_settings":{"type":"object","required":["welcome_email_enabled","verify_email_auto_send"],"properties":{"welcome_email_enabled":{"type":"boolean","description":"Send the built-in welcome email on user.created","default":false},"verify_email_auto_send":{"type":"boolean","description":"Send a verification email automatically when a user is created with `email_verified=false` and an email address. The link in the email lands on `GET /verify-email?ticket=...` and flips `email_verified=true` with `email_verified_method=verification_flow`. When `false`, verification emails must be requested explicitly via `POST /user/:id/verify-email/start`.","default":false}},"additionalProperties":false},"email_reply_to":{"type":"string","format":"email","maxLength":320,"description":"Reply-To on every email this tenant sends to its users. Absent = no Reply-To, so a reply goes to the From address — which is a `no-reply@` mailbox nobody reads. Set it to your own support address if you want your users to be able to answer.","nullable":true},"email_from_name":{"type":"string","maxLength":100,"description":"Display name on every email this tenant sends to its users. Absent = the account `name`.","nullable":true},"email_from_address":{"type":"string","description":"Sender address of every email this tenant sends to its users. Absent = the platform default. Set by Faable only.","nullable":true},"welcome_email":{"type":"object","properties":{"cta_url":{"type":"string","format":"uri","maxLength":500,"description":"Where the welcome button sends the user. Defaults to `https://<first callback hostname>`. Point it at your app (not your marketing site) so a fresh user lands somewhere useful.","nullable":true},"body":{"type":"string","maxLength":600,"description":"Replaces the default one-line body (\"Your account on … is ready to go.\"). Plain text, sent verbatim in every enabled locale.","nullable":true},"social_links":{"type":"array","items":{"type":"object","required":["label","url"],"properties":{"label":{"type":"string","minLength":1,"maxLength":40},"url":{"type":"string","format":"uri","maxLength":500}},"additionalProperties":false},"maxItems":6,"description":"Rendered as a \"Follow <account>\" block after the button — GitHub, LinkedIn, X, YouTube… Empty or absent hides the block.","nullable":true}},"additionalProperties":false,"nullable":true},"email_change_verification_mode":{"anyOf":[{"type":"string","enum":["new_only"]},{"type":"string","enum":["old_and_new"]}],"description":"Policy for the user email-change flow. `new_only` (default) sends a single confirmation link to the new email. `old_and_new` requires the user to also click a link sent to the previous email before the swap takes effect — stricter, useful for tenants with higher-risk users."},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]}],"description":"Controls what happens to `user.email` on subsequent OAuth/federated logins when the user previously changed their email manually through this auth server (i.e. `user.email_change_locked_at` is set).\n\n- `preserve_manual` (default): the manually-set email wins. The federated provider's email is ignored on re-sync; `email` and `email_verified` are not touched. The identity link stays valid via `provider_user_id`, so the user can still log in with Google/etc.\n- `always_sync`: the federated provider's email is always written back, overwriting any manual change. Useful for tenants whose source of truth for identity is the IdP (corporate SSO, etc.)."},"login_methods":{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"login_flow":{"type":"string","description":"The login flow bound to this account (`loginflow_xxx`). Absent = the flow compiled from the settings. A Client may bind its own.","nullable":true},"mfa_policy":{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},"recovery_channels":{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},"default_country_iso":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country assumed for phone numbers written without an international prefix (`636647460` → `+34…` with `ES`). Needed before SMS can reach anyone: most people type their number without a prefix.","nullable":true},"webauthn_rp_id":{"type":"string","description":"WebAuthn Relying Party ID for this tenant's passkeys. Defaults to the account domain. Set it to a registrable suffix you own (e.g. `acme.com`) when the login screen is served from more than one host — the RP ID is frozen into every credential at registration, so changing it afterwards invalidates every passkey already enrolled.","nullable":true},"logout_confirm_required":{"type":"boolean","description":"When true, `/logout` asks the End-User to confirm before ending the session unless the request carries a verified `id_token_hint`. Off by default — a plain link to `/logout` is otherwise enough to sign anyone out (logout CSRF)."},"dynamic_client_registration_enabled":{"type":"boolean","description":"When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`."},"oauth21_enabled":{"type":"boolean","description":"When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later."},"oauth_link_verified_email":{"type":"boolean","description":"When true, signing in with Google, GitHub, GitLab or Apple for the first time attaches that provider to the existing user with the same email instead of creating a second user — only when the provider attests the email as verified, exactly one user of the tenant has that email verified, that user is not suspended and has no other identity of the same connection. Off by default."},"client_id_metadata_documents_enabled":{"type":"boolean","description":"When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default."},"canonical_host":{"type":"string","description":"The one host this tenant's OIDC issuer and discovery document are always minted with, regardless of which host actually served the request — must be the account's own platform host or one of its verified custom domains. `/authorize` and `/logout` 302 here from any other host of the tenant. Unset (the default): every URL uses whatever host served the request, as before this field existed. Changing it can invalidate every WebAuthn passkey already enrolled unless `webauthn_rp_id` already covers the new host.","nullable":true},"createdAt":{"type":"string","description":"AuthAccount creation date"},"updatedAt":{"type":"string","description":"AuthAccount updated date"}},"description":"AuthAccount"}}}},"400":{"description":"`email_sender_domain_not_allowed` — The sender address is not in a domain this platform signs email for (AUTH_EMAIL_SENDER_DOMAINS).\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["email_sender_domain_not_allowed","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/connection":{"get":{"operationId":"connection/list","summary":"List Connections","tags":["connection"],"description":"List Connections","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"string"},"in":"query","name":"connection_type","required":false,"description":"Exact match on `connection_type`. Equivalent to `?query=connection_type:<value>`."},{"schema":{"type":"string"},"in":"query","name":"enabled","required":false,"description":"Exact match on `enabled`. Equivalent to `?query=enabled:<value>`."},{"schema":{"type":"string"},"in":"query","name":"category","required":false,"description":"Exact match on `category`. Equivalent to `?query=category:<value>`."},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"query","name":"q","required":false,"description":"Full-text search across: `connection_name`, `connection_type`."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Connection"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"connection/create","summary":"Create Connection","tags":["connection"],"description":"Create Connection","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["connection_name","connection_type"],"properties":{"connection_name":{"type":"string"},"connection_type":{"enum":["database","custom","github","figma","google_oauth2","microsoft","oidc","apple","facebook","linkedin","discord","gitlab","slack","x","twitch","bitbucket","spotify","passwordless_email"]},"authorize_url":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string"},"issuer":{"type":"string"},"jwks_url":{"type":"string"},"token_url":{"type":"string"},"userinfo_url":{"type":"string"},"organization":{"type":"string","description":"The Organization whose identity provider this is (`organization_…`), or `null`. Only `oidc` connections. Reached by home realm discovery from the organization's verified email domains, and only accepts emails of those domains. Pro plan."},"scope":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"claims_mapping":{"type":"object","additionalProperties":{"type":"string"}},"authorize_params":{"type":"object","description":"Authorize params","default":{},"additionalProperties":{"type":"string"}},"provider_options":{"type":"object","properties":{"team_id":{"type":"string","description":"Apple: the Apple Developer Team ID (10 characters)."},"key_id":{"type":"string","description":"Apple: the Key ID of the Sign in with Apple private key."}},"additionalProperties":false,"description":"Provider-specific settings beyond client_id/client_secret. Only `apple` uses them."},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]}],"description":"Controls what happens to `user.email` on subsequent OAuth/federated logins when the user previously changed their email manually through this auth server (i.e. `user.email_change_locked_at` is set).\n\n- `preserve_manual` (default): the manually-set email wins. The federated provider's email is ignored on re-sync; `email` and `email_verified` are not touched. The identity link stays valid via `provider_user_id`, so the user can still log in with Google/etc.\n- `always_sync`: the federated provider's email is always written back, overwriting any manual change. Useful for tenants whose source of truth for identity is the IdP (corporate SSO, etc.)."},"password_policy":{"type":"object","required":["level"],"properties":{"level":{"anyOf":[{"type":"string","enum":["none"]},{"type":"string","enum":["low"]},{"type":"string","enum":["fair"]},{"type":"string","enum":["good"]},{"type":"string","enum":["excellent"]}]},"min_length":{"type":"integer","minimum":1,"maximum":128},"require_lowercase":{"type":"boolean"},"require_uppercase":{"type":"boolean"},"require_number":{"type":"boolean"},"require_special":{"type":"boolean"}},"description":"Password policy enforced when writing a password through this database connection (password reset and admin set-password). `level` is an Auth0-style preset (none/low/fair/good/excellent); the dashboard expands it into the explicit rule fields when saving. The rule fields override the preset baseline. When unset on the connection, the server applies the `good` default. Does not affect login."},"disable_signup":{"type":"boolean"},"login_identifier":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["username"]},{"type":"string","enum":["email_or_username"]}],"description":"What users of this database connection sign in with: `email`, `username`, or `email_or_username` (email first, then username). The login looks up only the matching field, and the hosted login screen asks for it — validating the address format under `email`. New database connections default to `email`; a connection created before this setting existed behaves as `email_or_username`. Only meaningful on `database` connections."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"ConnectionCreate","additionalProperties":false}}},"description":"ConnectionCreate"},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Connection","content":{"application/json":{"schema":{"type":"object","required":["id","connection_name","connection_type","authorize_url","token_url","userinfo_url","client_id","client_secret","issuer","jwks_url","response_type","enabled","enabled_clients","scope","authorize_params","is_using_default_credentials","account","createdAt"],"properties":{"id":{"type":"string","description":"Connection ID"},"connection_name":{"type":"string"},"connection_type":{"enum":["database","custom","github","figma","google_oauth2","microsoft","oidc","apple","facebook","linkedin","discord","gitlab","slack","x","twitch","bitbucket","spotify","passwordless_email"]},"authorize_url":{"type":"string","default":null},"token_url":{"type":"string","default":null},"userinfo_url":{"type":"string","default":null},"client_id":{"type":"string","default":null},"client_secret":{"type":"string","default":null},"issuer":{"type":"string","default":null},"jwks_url":{"type":"string","default":null},"response_type":{"type":"string","default":null},"enabled":{"type":"boolean"},"organization":{"type":"string","description":"The Organization whose identity provider this is (`organization_…`), or `null`. Only `oidc` connections. Reached by home realm discovery from the organization's verified email domains, and only accepts emails of those domains. Pro plan.","nullable":true},"enabled_clients":{"type":"array","items":{"type":"string"},"default":null,"nullable":true},"scope":{"type":"array","items":{"type":"string"},"default":[]},"authorize_params":{"type":"object","description":"Authorize params","default":{},"additionalProperties":{"type":"string"}},"provider_options":{"type":"object","properties":{"team_id":{"type":"string","description":"Apple: the Apple Developer Team ID (10 characters)."},"key_id":{"type":"string","description":"Apple: the Key ID of the Sign in with Apple private key."}},"additionalProperties":false,"description":"Provider-specific settings beyond client_id/client_secret. Only `apple` uses them."},"claims_mapping":{"type":"object","additionalProperties":{"type":"string"}},"is_using_default_credentials":{"type":"boolean","readOnly":true,"description":"Derived: true when the connection_type has shared Faable defaults AND the tenant has not set its own client_id/client_secret. Read-only — set by the server on every read."},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]}],"description":"Per-connection override of `Account.email_oauth_sync_policy`. When set on a Connection, takes precedence over the Account-level setting for logins through this IdP. When unset (default), the Account-level policy applies. Use this to allow `always_sync` for a trusted IdP (e.g. corporate SSO) while keeping `preserve_manual` for others within the same tenant."},"password_policy":{"type":"object","required":["level"],"properties":{"level":{"anyOf":[{"type":"string","enum":["none"]},{"type":"string","enum":["low"]},{"type":"string","enum":["fair"]},{"type":"string","enum":["good"]},{"type":"string","enum":["excellent"]}]},"min_length":{"type":"integer","minimum":1,"maximum":128},"require_lowercase":{"type":"boolean"},"require_uppercase":{"type":"boolean"},"require_number":{"type":"boolean"},"require_special":{"type":"boolean"}},"description":"Password policy enforced when writing a password through this database connection (password reset and admin set-password). `level` is an Auth0-style preset (none/low/fair/good/excellent); the dashboard expands it into the explicit rule fields when saving. The rule fields override the preset baseline. When unset on the connection, the server applies the `good` default. Does not affect login."},"login_identifier":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["username"]},{"type":"string","enum":["email_or_username"]}],"description":"What users of this database connection sign in with: `email`, `username`, or `email_or_username` (email first, then username). The login looks up only the matching field, and the hosted login screen asks for it — validating the address format under `email`. New database connections default to `email`; a connection created before this setting existed behaves as `email_or_username`. Only meaningful on `database` connections."},"disable_signup":{"type":"boolean","description":"When true, the public self-service signup endpoint (`POST /dbconnections/signup`) rejects new registrations against this database connection with `403 signup_disabled`. Defaults to false (signup allowed). Only meaningful on `database` connections; mirrors Auth0 `disable_signup`."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ConnectionMetadata","default":{}},"createdAt":{"type":"string","description":"Connection creation date"},"updatedAt":{"type":"string","description":"Connection updated date"}},"description":"Connection"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/connection/{connection_id}":{"get":{"operationId":"connection/get","summary":"Get Connection","tags":["connection"],"description":"Get Connection","parameters":[{"schema":{"type":"string"},"in":"path","name":"connection_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Connection","content":{"application/json":{"schema":{"type":"object","required":["id","connection_name","connection_type","authorize_url","token_url","userinfo_url","client_id","client_secret","issuer","jwks_url","response_type","enabled","enabled_clients","scope","authorize_params","is_using_default_credentials","account","createdAt"],"properties":{"id":{"type":"string","description":"Connection ID"},"connection_name":{"type":"string"},"connection_type":{"enum":["database","custom","github","figma","google_oauth2","microsoft","oidc","apple","facebook","linkedin","discord","gitlab","slack","x","twitch","bitbucket","spotify","passwordless_email"]},"authorize_url":{"type":"string","default":null},"token_url":{"type":"string","default":null},"userinfo_url":{"type":"string","default":null},"client_id":{"type":"string","default":null},"client_secret":{"type":"string","default":null},"issuer":{"type":"string","default":null},"jwks_url":{"type":"string","default":null},"response_type":{"type":"string","default":null},"enabled":{"type":"boolean"},"organization":{"type":"string","description":"The Organization whose identity provider this is (`organization_…`), or `null`. Only `oidc` connections. Reached by home realm discovery from the organization's verified email domains, and only accepts emails of those domains. Pro plan.","nullable":true},"enabled_clients":{"type":"array","items":{"type":"string"},"default":null,"nullable":true},"scope":{"type":"array","items":{"type":"string"},"default":[]},"authorize_params":{"type":"object","description":"Authorize params","default":{},"additionalProperties":{"type":"string"}},"provider_options":{"type":"object","properties":{"team_id":{"type":"string","description":"Apple: the Apple Developer Team ID (10 characters)."},"key_id":{"type":"string","description":"Apple: the Key ID of the Sign in with Apple private key."}},"additionalProperties":false,"description":"Provider-specific settings beyond client_id/client_secret. Only `apple` uses them."},"claims_mapping":{"type":"object","additionalProperties":{"type":"string"}},"is_using_default_credentials":{"type":"boolean","readOnly":true,"description":"Derived: true when the connection_type has shared Faable defaults AND the tenant has not set its own client_id/client_secret. Read-only — set by the server on every read."},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]}],"description":"Per-connection override of `Account.email_oauth_sync_policy`. When set on a Connection, takes precedence over the Account-level setting for logins through this IdP. When unset (default), the Account-level policy applies. Use this to allow `always_sync` for a trusted IdP (e.g. corporate SSO) while keeping `preserve_manual` for others within the same tenant."},"password_policy":{"type":"object","required":["level"],"properties":{"level":{"anyOf":[{"type":"string","enum":["none"]},{"type":"string","enum":["low"]},{"type":"string","enum":["fair"]},{"type":"string","enum":["good"]},{"type":"string","enum":["excellent"]}]},"min_length":{"type":"integer","minimum":1,"maximum":128},"require_lowercase":{"type":"boolean"},"require_uppercase":{"type":"boolean"},"require_number":{"type":"boolean"},"require_special":{"type":"boolean"}},"description":"Password policy enforced when writing a password through this database connection (password reset and admin set-password). `level` is an Auth0-style preset (none/low/fair/good/excellent); the dashboard expands it into the explicit rule fields when saving. The rule fields override the preset baseline. When unset on the connection, the server applies the `good` default. Does not affect login."},"login_identifier":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["username"]},{"type":"string","enum":["email_or_username"]}],"description":"What users of this database connection sign in with: `email`, `username`, or `email_or_username` (email first, then username). The login looks up only the matching field, and the hosted login screen asks for it — validating the address format under `email`. New database connections default to `email`; a connection created before this setting existed behaves as `email_or_username`. Only meaningful on `database` connections."},"disable_signup":{"type":"boolean","description":"When true, the public self-service signup endpoint (`POST /dbconnections/signup`) rejects new registrations against this database connection with `403 signup_disabled`. Defaults to false (signup allowed). Only meaningful on `database` connections; mirrors Auth0 `disable_signup`."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ConnectionMetadata","default":{}},"createdAt":{"type":"string","description":"Connection creation date"},"updatedAt":{"type":"string","description":"Connection updated date"}},"description":"Connection"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"connection/update","summary":"Update Connection","tags":["connection"],"description":"Update Connection","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"connection_name":{"type":"string"},"enabled":{"type":"boolean"},"enabled_clients":{"type":"array","items":{"type":"string"}},"scope":{"type":"array","items":{"type":"string"}},"client_id":{"type":"string"},"client_secret":{"type":"string"},"issuer":{"type":"string"},"authorize_url":{"type":"string"},"token_url":{"type":"string"},"userinfo_url":{"type":"string"},"jwks_url":{"type":"string"},"response_type":{"type":"string"},"authorize_params":{"type":"object","description":"Authorize params","default":{},"additionalProperties":{"type":"string"}},"provider_options":{"type":"object","properties":{"team_id":{"type":"string","description":"Apple: the Apple Developer Team ID (10 characters)."},"key_id":{"type":"string","description":"Apple: the Key ID of the Sign in with Apple private key."}},"additionalProperties":false,"description":"Provider-specific settings beyond client_id/client_secret. Only `apple` uses them."},"claims_mapping":{"type":"object","additionalProperties":{"type":"string"}},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]},{"type":"null"}]},"password_policy":{"anyOf":[{"type":"object","required":["level"],"properties":{"level":{"anyOf":[{"type":"string","enum":["none"]},{"type":"string","enum":["low"]},{"type":"string","enum":["fair"]},{"type":"string","enum":["good"]},{"type":"string","enum":["excellent"]}]},"min_length":{"type":"integer","minimum":1,"maximum":128},"require_lowercase":{"type":"boolean"},"require_uppercase":{"type":"boolean"},"require_number":{"type":"boolean"},"require_special":{"type":"boolean"}},"description":"Password policy enforced when writing a password through this database connection (password reset and admin set-password). `level` is an Auth0-style preset (none/low/fair/good/excellent); the dashboard expands it into the explicit rule fields when saving. The rule fields override the preset baseline. When unset on the connection, the server applies the `good` default. Does not affect login."},{"type":"null"}]},"disable_signup":{"type":"boolean"},"login_identifier":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["username"]},{"type":"string","enum":["email_or_username"]}],"description":"What users of this database connection sign in with: `email`, `username`, or `email_or_username` (email first, then username). The login looks up only the matching field, and the hosted login screen asks for it — validating the address format under `email`. New database connections default to `email`; a connection created before this setting existed behaves as `email_or_username`. Only meaningful on `database` connections."},"organization":{"type":"string","nullable":true}},"description":"Partial update for a Connection. Only the supplied fields are modified. `connection_type` is intentionally excluded — changing it would orphan every identity already linked to this connection.","additionalProperties":false}}},"description":"Partial update for a Connection. Only the supplied fields are modified. `connection_type` is intentionally excluded — changing it would orphan every identity already linked to this connection."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"connection_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Connection","content":{"application/json":{"schema":{"type":"object","required":["id","connection_name","connection_type","authorize_url","token_url","userinfo_url","client_id","client_secret","issuer","jwks_url","response_type","enabled","enabled_clients","scope","authorize_params","is_using_default_credentials","account","createdAt"],"properties":{"id":{"type":"string","description":"Connection ID"},"connection_name":{"type":"string"},"connection_type":{"enum":["database","custom","github","figma","google_oauth2","microsoft","oidc","apple","facebook","linkedin","discord","gitlab","slack","x","twitch","bitbucket","spotify","passwordless_email"]},"authorize_url":{"type":"string","default":null},"token_url":{"type":"string","default":null},"userinfo_url":{"type":"string","default":null},"client_id":{"type":"string","default":null},"client_secret":{"type":"string","default":null},"issuer":{"type":"string","default":null},"jwks_url":{"type":"string","default":null},"response_type":{"type":"string","default":null},"enabled":{"type":"boolean"},"organization":{"type":"string","description":"The Organization whose identity provider this is (`organization_…`), or `null`. Only `oidc` connections. Reached by home realm discovery from the organization's verified email domains, and only accepts emails of those domains. Pro plan.","nullable":true},"enabled_clients":{"type":"array","items":{"type":"string"},"default":null,"nullable":true},"scope":{"type":"array","items":{"type":"string"},"default":[]},"authorize_params":{"type":"object","description":"Authorize params","default":{},"additionalProperties":{"type":"string"}},"provider_options":{"type":"object","properties":{"team_id":{"type":"string","description":"Apple: the Apple Developer Team ID (10 characters)."},"key_id":{"type":"string","description":"Apple: the Key ID of the Sign in with Apple private key."}},"additionalProperties":false,"description":"Provider-specific settings beyond client_id/client_secret. Only `apple` uses them."},"claims_mapping":{"type":"object","additionalProperties":{"type":"string"}},"is_using_default_credentials":{"type":"boolean","readOnly":true,"description":"Derived: true when the connection_type has shared Faable defaults AND the tenant has not set its own client_id/client_secret. Read-only — set by the server on every read."},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]}],"description":"Per-connection override of `Account.email_oauth_sync_policy`. When set on a Connection, takes precedence over the Account-level setting for logins through this IdP. When unset (default), the Account-level policy applies. Use this to allow `always_sync` for a trusted IdP (e.g. corporate SSO) while keeping `preserve_manual` for others within the same tenant."},"password_policy":{"type":"object","required":["level"],"properties":{"level":{"anyOf":[{"type":"string","enum":["none"]},{"type":"string","enum":["low"]},{"type":"string","enum":["fair"]},{"type":"string","enum":["good"]},{"type":"string","enum":["excellent"]}]},"min_length":{"type":"integer","minimum":1,"maximum":128},"require_lowercase":{"type":"boolean"},"require_uppercase":{"type":"boolean"},"require_number":{"type":"boolean"},"require_special":{"type":"boolean"}},"description":"Password policy enforced when writing a password through this database connection (password reset and admin set-password). `level` is an Auth0-style preset (none/low/fair/good/excellent); the dashboard expands it into the explicit rule fields when saving. The rule fields override the preset baseline. When unset on the connection, the server applies the `good` default. Does not affect login."},"login_identifier":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["username"]},{"type":"string","enum":["email_or_username"]}],"description":"What users of this database connection sign in with: `email`, `username`, or `email_or_username` (email first, then username). The login looks up only the matching field, and the hosted login screen asks for it — validating the address format under `email`. New database connections default to `email`; a connection created before this setting existed behaves as `email_or_username`. Only meaningful on `database` connections."},"disable_signup":{"type":"boolean","description":"When true, the public self-service signup endpoint (`POST /dbconnections/signup`) rejects new registrations against this database connection with `403 signup_disabled`. Defaults to false (signup allowed). Only meaningful on `database` connections; mirrors Auth0 `disable_signup`."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ConnectionMetadata","default":{}},"createdAt":{"type":"string","description":"Connection creation date"},"updatedAt":{"type":"string","description":"Connection updated date"}},"description":"Connection"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"connection/delete","summary":"Delete Connection","tags":["connection"],"description":"Delete Connection","parameters":[{"schema":{"type":"string"},"in":"path","name":"connection_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Connection","content":{"application/json":{"schema":{"type":"object","required":["id","connection_name","connection_type","authorize_url","token_url","userinfo_url","client_id","client_secret","issuer","jwks_url","response_type","enabled","enabled_clients","scope","authorize_params","is_using_default_credentials","account","createdAt"],"properties":{"id":{"type":"string","description":"Connection ID"},"connection_name":{"type":"string"},"connection_type":{"enum":["database","custom","github","figma","google_oauth2","microsoft","oidc","apple","facebook","linkedin","discord","gitlab","slack","x","twitch","bitbucket","spotify","passwordless_email"]},"authorize_url":{"type":"string","default":null},"token_url":{"type":"string","default":null},"userinfo_url":{"type":"string","default":null},"client_id":{"type":"string","default":null},"client_secret":{"type":"string","default":null},"issuer":{"type":"string","default":null},"jwks_url":{"type":"string","default":null},"response_type":{"type":"string","default":null},"enabled":{"type":"boolean"},"organization":{"type":"string","description":"The Organization whose identity provider this is (`organization_…`), or `null`. Only `oidc` connections. Reached by home realm discovery from the organization's verified email domains, and only accepts emails of those domains. Pro plan.","nullable":true},"enabled_clients":{"type":"array","items":{"type":"string"},"default":null,"nullable":true},"scope":{"type":"array","items":{"type":"string"},"default":[]},"authorize_params":{"type":"object","description":"Authorize params","default":{},"additionalProperties":{"type":"string"}},"provider_options":{"type":"object","properties":{"team_id":{"type":"string","description":"Apple: the Apple Developer Team ID (10 characters)."},"key_id":{"type":"string","description":"Apple: the Key ID of the Sign in with Apple private key."}},"additionalProperties":false,"description":"Provider-specific settings beyond client_id/client_secret. Only `apple` uses them."},"claims_mapping":{"type":"object","additionalProperties":{"type":"string"}},"is_using_default_credentials":{"type":"boolean","readOnly":true,"description":"Derived: true when the connection_type has shared Faable defaults AND the tenant has not set its own client_id/client_secret. Read-only — set by the server on every read."},"email_oauth_sync_policy":{"anyOf":[{"type":"string","enum":["preserve_manual"]},{"type":"string","enum":["always_sync"]}],"description":"Per-connection override of `Account.email_oauth_sync_policy`. When set on a Connection, takes precedence over the Account-level setting for logins through this IdP. When unset (default), the Account-level policy applies. Use this to allow `always_sync` for a trusted IdP (e.g. corporate SSO) while keeping `preserve_manual` for others within the same tenant."},"password_policy":{"type":"object","required":["level"],"properties":{"level":{"anyOf":[{"type":"string","enum":["none"]},{"type":"string","enum":["low"]},{"type":"string","enum":["fair"]},{"type":"string","enum":["good"]},{"type":"string","enum":["excellent"]}]},"min_length":{"type":"integer","minimum":1,"maximum":128},"require_lowercase":{"type":"boolean"},"require_uppercase":{"type":"boolean"},"require_number":{"type":"boolean"},"require_special":{"type":"boolean"}},"description":"Password policy enforced when writing a password through this database connection (password reset and admin set-password). `level` is an Auth0-style preset (none/low/fair/good/excellent); the dashboard expands it into the explicit rule fields when saving. The rule fields override the preset baseline. When unset on the connection, the server applies the `good` default. Does not affect login."},"login_identifier":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["username"]},{"type":"string","enum":["email_or_username"]}],"description":"What users of this database connection sign in with: `email`, `username`, or `email_or_username` (email first, then username). The login looks up only the matching field, and the hosted login screen asks for it — validating the address format under `email`. New database connections default to `email`; a connection created before this setting existed behaves as `email_or_username`. Only meaningful on `database` connections."},"disable_signup":{"type":"boolean","description":"When true, the public self-service signup endpoint (`POST /dbconnections/signup`) rejects new registrations against this database connection with `403 signup_disabled`. Defaults to false (signup allowed). Only meaningful on `database` connections; mirrors Auth0 `disable_signup`."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ConnectionMetadata","default":{}},"createdAt":{"type":"string","description":"Connection creation date"},"updatedAt":{"type":"string","description":"Connection updated date"}},"description":"Connection"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/connection-types":{"get":{"operationId":"connection/types","summary":"List supported connection types","tags":["connection"],"description":"Returns the catalog of connection types this server supports, with the presentation metadata a UI needs (label, icon slug, category) and two derived capability flags: whether the provider comes preconfigured and whether Faable ships shared credentials for it. Public: contains no URLs and no credentials. Changes only between server releases — cache it.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ConnectionTypeInfo"}}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/login-options":{"get":{"operationId":"connection/loginOptions","summary":"Resolve the login methods for a client","tags":["connection"],"description":"Returns the ordered list of login methods the hosted login screen should offer for this client, already filtered by `connection.enabled` and `connection.enabled_clients`. `client_id` is the OAuth client_id; when omitted the server resolves one: the account's only client, or its only client with `callbacks` (the ones that could plausibly be behind a login screen), and failing that the first of those two lists in order. `client` comes back `null` only for an account with no clients at all, and `methods` is always resolved against the client that comes back.","parameters":[{"schema":{"type":"string"},"in":"query","name":"client_id","required":false},{"schema":{"type":"string"},"in":"query","name":"state","required":false,"description":"The login state the screen was opened with. When the flow put an identifier-first screen before this one, the identifier the user typed comes back as `identifier`."},{"schema":{"type":"string"},"in":"query","name":"last_used","required":false,"description":"The method key (`passkey` or a connection id) this browser used last time, as remembered by the hosted UI. Honoured only when the tenant enabled `remember_last_method`: that method is moved first and echoed as `last_used`."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["account","client","identifier_first","remember_last_method","remember_me","methods"],"properties":{"account":{"type":"object","required":["id","name","domain","logo_src","icon_src"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"domain":{"type":"string","description":"The tenant's auth host. The login screen talks to this host for every call it makes after the first paint, so it has to come back here: a screen that is served as a static bundle has no other way to learn it, and asking the management API for it is what used to force the hosted UI to hold a platform credential."},"logo_src":{"anyOf":[{"type":"string"},{"type":"null"}]},"icon_src":{"anyOf":[{"type":"string"},{"type":"null"}]}}},"client":{"anyOf":[{"type":"object","required":["id","client_id","name","origins"],"properties":{"id":{"type":"string"},"client_id":{"type":"string"},"name":{"type":"string"},"origins":{"type":"array","items":{"type":"string"},"description":"The http(s) origins this client registered in its `callbacks`, de-duplicated. A hosted screen uses it as the allow-list for a cross-origin `return_to`: the account security page is opened from the tenant's own application and has to offer a way back to it, which `redirect_to` (same-origin paths only) cannot express. Nothing secret — a redirect URI already travels in the query of every `/authorize` call."},"initiate_login_uri":{"type":"string","description":"The client's `initiate_login_uri`: the URL in the application that starts a fresh login. The hosted screen sends people there when the login link they opened is expired or was opened in another browser. Absent when the client does not set one."}}},{"type":"null"}],"description":"The client the methods were resolved for, or `null` when none could be determined (no `client_id` given and the account has zero or several clients)."},"identifier_first":{"type":"boolean"},"remember_last_method":{"type":"boolean"},"remember_me":{"type":"boolean","description":"Render a \"Remember me on this device\" checkbox on the password and email-code forms, and send its value as `remember_me` when posting the credential."},"identifier":{"type":"string","description":"What the user typed on the identifier-first screen, when the flow has one. Prefill the email field with it."},"last_used":{"type":"string","description":"The method key that was moved first because this browser used it last time (`remember_last_method`). Absent otherwise."},"theme":{"type":"string","enum":["light","dark"],"description":"The colour theme the screens are forced to, and the visitor's switch hidden: `theme` on this login's /authorize, else the tenant/client `login_methods.theme`. Absent when nothing forces it — the visitor's own choice, or the OS, decides."},"state_status":{"type":"string","enum":["live","expired","foreign"],"description":"What became of the login `state` the screen was opened with. `live`: the login can go on. `expired`: it no longer exists — a tab left open too long, or a login URL that was bookmarked or shared — and no credential posted against it will work, so start a new login from the application. `foreign`: it exists but was started in another browser, which is what a shared login URL looks like; the login would not reach the application that asked for it. Absent when no `state` was sent or it belongs to another client."},"methods":{"type":"array","items":{"$ref":"#/components/schemas/LoginOptionMethod"},"description":"Already filtered and ordered. Render them in this order; do not re-sort or re-filter client-side."}},"additionalProperties":false}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/login-host":{"get":{"operationId":"client/loginHost","summary":"Find the login host of a client's tenant","tags":["client"],"description":"The host where the hosted login screens of the tenant that owns this `client_id` are served. Lets a login URL opened on the wrong host be sent to the right one. Not tenant-scoped: callable on any auth host.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"query","name":"client_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["host"],"properties":{"host":{"type":"string","description":"The tenant's login host: its `canonical_host` when set, else its platform host."}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/login/home-realm":{"get":{"operationId":"login/homeRealm","summary":"Where an email signs in (home realm discovery)","tags":["connection"],"description":"If the identifier is an email of an organization's verified domain and that organization has its own identity provider, its connection: start the login with `connection=<connection_id>` instead of a password or code. `home_realm: null` for everybody else.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":320},"in":"query","name":"identifier","required":true},{"schema":{"type":"string","maxLength":200},"in":"query","name":"client_id","required":false}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["home_realm"],"properties":{"home_realm":{"anyOf":[{"type":"object","required":["connection_id","organization"],"properties":{"connection_id":{"type":"string"},"organization":{"type":"object","required":["id","name","logo_url"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}]}}}}},{"type":"null"}]}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/login/identify":{"post":{"operationId":"login/identify","summary":"Leave the identifier-first screen","tags":["connection"],"description":"Records the identifier the user typed on the hosted `/flow/identify` screen and advances the login to the method chooser. `state` is the id the screen was opened with; it stays valid, so the chooser is reached with the same `?state=`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state","identifier"],"properties":{"state":{"type":"string"},"identifier":{"type":"string","minLength":1,"maxLength":320}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"},"home_realm":{"type":"object","required":["connection_id","organization"],"properties":{"connection_id":{"type":"string"},"organization":{"type":"object","required":["id","name","logo_url"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}]}}}},"description":"The identifier is an email of an organization's verified domain, and that organization has its own identity provider: continue with `/authorize?resume_state=<state>&connection=<connection_id>` instead of the method chooser (arch/auth/sso.md F3)."}}}}}},"400":{"description":"`invalid_request` — A required OAuth parameter is missing or two of them are incompatible. `message` says which.\n\n`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_request","invalid_state","validation_error"]}}}]}}}},"401":{"description":"`state_mismatch` — The `state` belongs to another account, session, client or ceremony.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["state_mismatch"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/client":{"get":{"operationId":"client/list","summary":"List Clients","tags":["client"],"description":"List Clients","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"query","name":"q","required":false,"description":"Full-text search across: `name`, `description`, `client_id`."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Client"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"client/create","summary":"Create Client","tags":["client"],"description":"Create Client","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"callbacks":{"type":"array","items":{"type":"string"}},"logout_urls":{"type":"array","items":{"type":"string"}},"web_origins":{"type":"array","items":{"type":"string"}},"initiate_login_uri":{"type":"string"},"grant_types":{"type":"array","items":{"anyOf":[{"type":"string","enum":["authorization_code"]},{"type":"string","enum":["client_credentials"]},{"type":"string","enum":["refresh_token"]},{"type":"string","enum":["password"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/passwordless/otp"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:token-exchange"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:device_code"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-otp"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-recovery-code"]}],"description":"A grant the client may use at the token endpoint. An empty `grant_types` list allows every grant except `password`, which is always opt-in."},"uniqueItems":true},"management_scopes":{"type":"array","items":{"type":"string"},"uniqueItems":true,"description":"Management API scopes (e.g. `read:rolemembers`) this client may obtain with client_credentials for the audience `faable:management:<account_id>`. A token request gets the requested scopes that are in this list, or the whole list when it asks for none. Empty = the client cannot obtain Management API tokens. Only scopes your own token holds can be granted."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"ClientCreate","additionalProperties":false}}},"description":"ClientCreate"},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Client","content":{"application/json":{"schema":{"type":"object","required":["id","name","description","client_id","client_secret","callbacks","logout_urls","web_origins","refresh_token","account","createdAt"],"properties":{"id":{"type":"string","description":"Client ID"},"name":{"type":"string"},"description":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string"},"callbacks":{"type":"array","items":{"type":"string"}},"logout_urls":{"type":"array","items":{"type":"string"}},"web_origins":{"type":"array","items":{"type":"string"},"description":"Origins allowed to make cross-origin (CORS) requests to the token and passwordless endpoints. Empty = any origin."},"refresh_token":{"type":"object","required":["expiration_mode","infinite_token_lifetime","token_lifetime"],"properties":{"expiration_mode":{"enum":["expire","not-expire"]},"infinite_token_lifetime":{"type":"boolean"},"token_lifetime":{"type":"number"}}},"access_token":{"type":"object","properties":{"token_lifetime":{"type":"number","minimum":60,"description":"Access token lifetime in seconds for tokens issued to this client. A resolved audience (Api) token_lifetime takes precedence. Omit to use the tenant default (24h)."}}},"client_uri":{"type":"string","nullable":true},"logo_uri":{"type":"string","nullable":true},"tos_uri":{"type":"string","nullable":true},"policy_uri":{"type":"string","nullable":true},"initiate_login_uri":{"type":"string","description":"The URL in the application that starts a login. Where the hosted login screen sends people who open an expired or shared login link.","nullable":true},"contacts":{"type":"array","items":{"type":"string"}},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string"},"consent_required":{"type":"boolean","description":"When true, /authorize never issues a code in silence for this client: the End-User approves it on the consent screen first. Set on clients the tenant did not create (Client ID Metadata Documents, dynamic registration)."},"metadata_document_url":{"type":"string","description":"For a client identified by a Client ID Metadata Document: the URL (also its client_id) its metadata was fetched from."},"registered_dynamically":{"type":"boolean"},"application_type":{"type":"string"},"software_id":{"type":"string","nullable":true},"software_version":{"type":"string","nullable":true},"frontchannel_logout_uri":{"type":"string","nullable":true},"frontchannel_logout_session_required":{"type":"boolean"},"backchannel_logout_uri":{"type":"string","nullable":true},"backchannel_logout_session_required":{"type":"boolean"},"login_methods":{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"mfa_policy":{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},"recovery_channels":{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},"login_flow":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A login flow bound to this client (`loginflow_xxx`), overriding the account's. Absent = inherit."},"default_audience":{"type":"string","description":"Audience used when a client_credentials token request sends none (e.g. `faable:management:<account_id>`). An explicit `audience` in the request wins. Absent = `${iss}/userinfo`.","nullable":true},"management_scopes":{"type":"array","items":{"type":"string"},"description":"Management API scopes (e.g. `read:rolemembers`) this client may obtain with client_credentials for the audience `faable:management:<account_id>`. A token request gets the requested scopes that are in this list, or the whole list when it asks for none. Empty = the client cannot obtain Management API tokens."},"saml_sp":{"type":"object","required":["enabled","entity_id","acs_url"],"properties":{"enabled":{"type":"boolean","description":"Whether this client answers SAML requests. Off keeps the configuration but every /saml route of the client returns 404."},"entity_id":{"type":"string","minLength":1,"maxLength":1024,"description":"The Service Provider's Entity ID. Its AuthnRequests must carry it as Issuer, and every assertion is restricted to it as Audience."},"acs_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The Service Provider's Assertion Consumer Service URL (HTTP-POST binding), https. The only URL a response is ever posted to: an AuthnRequest naming another one is refused."},"nameid_format":{"enum":["emailAddress","persistent","unspecified"],"description":"`emailAddress` (default) sends the email as NameID; `persistent` sends the stable user id; `unspecified` sends the email when the user has one, the user id otherwise."},"attribute_mapping":{"type":"object","description":"SAML attribute name → user field. Omit for the default (email, name, given_name, family_name). Custom ID token claims set by post-login actions are added as attributes too.","additionalProperties":{"anyOf":[{"type":"string","enum":["user_id"]},{"type":"string","enum":["email"]},{"type":"string","enum":["email_verified"]},{"type":"string","enum":["name"]},{"type":"string","enum":["given_name"]},{"type":"string","enum":["family_name"]},{"type":"string","enum":["nickname"]},{"type":"string","enum":["picture"]},{"type":"string","enum":["phone_number"]}]}},"sign_response":{"type":"boolean","description":"Also sign the Response envelope, not only the Assertion. Default true; a few SPs reject a doubly-signed message."}},"additionalProperties":false,"nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ClientMetadata","default":{}},"createdAt":{"type":"string","description":"Client creation date"},"updatedAt":{"type":"string","description":"Client updated date"}},"description":"Client"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/client/{client_id}":{"get":{"operationId":"client/get","summary":"Get Client","tags":["client"],"description":"Get Client","parameters":[{"schema":{"type":"string"},"in":"path","name":"client_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Client","content":{"application/json":{"schema":{"type":"object","required":["id","name","description","client_id","client_secret","callbacks","logout_urls","web_origins","refresh_token","account","createdAt"],"properties":{"id":{"type":"string","description":"Client ID"},"name":{"type":"string"},"description":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string"},"callbacks":{"type":"array","items":{"type":"string"}},"logout_urls":{"type":"array","items":{"type":"string"}},"web_origins":{"type":"array","items":{"type":"string"},"description":"Origins allowed to make cross-origin (CORS) requests to the token and passwordless endpoints. Empty = any origin."},"refresh_token":{"type":"object","required":["expiration_mode","infinite_token_lifetime","token_lifetime"],"properties":{"expiration_mode":{"enum":["expire","not-expire"]},"infinite_token_lifetime":{"type":"boolean"},"token_lifetime":{"type":"number"}}},"access_token":{"type":"object","properties":{"token_lifetime":{"type":"number","minimum":60,"description":"Access token lifetime in seconds for tokens issued to this client. A resolved audience (Api) token_lifetime takes precedence. Omit to use the tenant default (24h)."}}},"client_uri":{"type":"string","nullable":true},"logo_uri":{"type":"string","nullable":true},"tos_uri":{"type":"string","nullable":true},"policy_uri":{"type":"string","nullable":true},"initiate_login_uri":{"type":"string","description":"The URL in the application that starts a login. Where the hosted login screen sends people who open an expired or shared login link.","nullable":true},"contacts":{"type":"array","items":{"type":"string"}},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string"},"consent_required":{"type":"boolean","description":"When true, /authorize never issues a code in silence for this client: the End-User approves it on the consent screen first. Set on clients the tenant did not create (Client ID Metadata Documents, dynamic registration)."},"metadata_document_url":{"type":"string","description":"For a client identified by a Client ID Metadata Document: the URL (also its client_id) its metadata was fetched from."},"registered_dynamically":{"type":"boolean"},"application_type":{"type":"string"},"software_id":{"type":"string","nullable":true},"software_version":{"type":"string","nullable":true},"frontchannel_logout_uri":{"type":"string","nullable":true},"frontchannel_logout_session_required":{"type":"boolean"},"backchannel_logout_uri":{"type":"string","nullable":true},"backchannel_logout_session_required":{"type":"boolean"},"login_methods":{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"mfa_policy":{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},"recovery_channels":{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},"login_flow":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A login flow bound to this client (`loginflow_xxx`), overriding the account's. Absent = inherit."},"default_audience":{"type":"string","description":"Audience used when a client_credentials token request sends none (e.g. `faable:management:<account_id>`). An explicit `audience` in the request wins. Absent = `${iss}/userinfo`.","nullable":true},"management_scopes":{"type":"array","items":{"type":"string"},"description":"Management API scopes (e.g. `read:rolemembers`) this client may obtain with client_credentials for the audience `faable:management:<account_id>`. A token request gets the requested scopes that are in this list, or the whole list when it asks for none. Empty = the client cannot obtain Management API tokens."},"saml_sp":{"type":"object","required":["enabled","entity_id","acs_url"],"properties":{"enabled":{"type":"boolean","description":"Whether this client answers SAML requests. Off keeps the configuration but every /saml route of the client returns 404."},"entity_id":{"type":"string","minLength":1,"maxLength":1024,"description":"The Service Provider's Entity ID. Its AuthnRequests must carry it as Issuer, and every assertion is restricted to it as Audience."},"acs_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The Service Provider's Assertion Consumer Service URL (HTTP-POST binding), https. The only URL a response is ever posted to: an AuthnRequest naming another one is refused."},"nameid_format":{"enum":["emailAddress","persistent","unspecified"],"description":"`emailAddress` (default) sends the email as NameID; `persistent` sends the stable user id; `unspecified` sends the email when the user has one, the user id otherwise."},"attribute_mapping":{"type":"object","description":"SAML attribute name → user field. Omit for the default (email, name, given_name, family_name). Custom ID token claims set by post-login actions are added as attributes too.","additionalProperties":{"anyOf":[{"type":"string","enum":["user_id"]},{"type":"string","enum":["email"]},{"type":"string","enum":["email_verified"]},{"type":"string","enum":["name"]},{"type":"string","enum":["given_name"]},{"type":"string","enum":["family_name"]},{"type":"string","enum":["nickname"]},{"type":"string","enum":["picture"]},{"type":"string","enum":["phone_number"]}]}},"sign_response":{"type":"boolean","description":"Also sign the Response envelope, not only the Assertion. Default true; a few SPs reject a doubly-signed message."}},"additionalProperties":false,"nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ClientMetadata","default":{}},"createdAt":{"type":"string","description":"Client creation date"},"updatedAt":{"type":"string","description":"Client updated date"}},"description":"Client"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"client/update","summary":"Update Client","tags":["client"],"description":"Update Client","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable client name shown in consent screens."},"description":{"type":"string","description":"Free-form description for internal admin use."},"callbacks":{"type":"array","items":{"type":"string"},"description":"Whitelist of allowed OAuth `redirect_uri` values."},"logout_urls":{"type":"array","items":{"type":"string"},"description":"Whitelist of allowed `post_logout_redirect_uri` values."},"web_origins":{"type":"array","items":{"type":"string"},"description":"Whitelist of origins (scheme://host[:port], no path) allowed to make cross-origin (CORS) requests to the token and passwordless endpoints. Empty allows any origin."},"refresh_token":{"type":"object","required":["expiration_mode","infinite_token_lifetime","token_lifetime"],"properties":{"expiration_mode":{"enum":["expire","not-expire"]},"infinite_token_lifetime":{"type":"boolean"},"token_lifetime":{"type":"number"}}},"access_token":{"type":"object","properties":{"token_lifetime":{"type":"number","minimum":60,"description":"Access token lifetime in seconds for tokens issued to this client. A resolved audience (Api) token_lifetime takes precedence. Omit to use the tenant default (24h)."}}},"client_uri":{"type":"string","nullable":true},"logo_uri":{"type":"string","nullable":true},"tos_uri":{"type":"string","nullable":true},"policy_uri":{"type":"string","nullable":true},"initiate_login_uri":{"type":"string","description":"OIDC `initiate_login_uri`: the URL in your application that starts a login (it redirects to /authorize). When someone opens an expired or shared login link, the hosted login screen sends them here to start over. https only (http on localhost). `null` removes it.","nullable":true},"contacts":{"type":"array","items":{"type":"string"}},"grant_types":{"type":"array","items":{"anyOf":[{"type":"string","enum":["authorization_code"]},{"type":"string","enum":["client_credentials"]},{"type":"string","enum":["refresh_token"]},{"type":"string","enum":["password"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/passwordless/otp"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:token-exchange"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:device_code"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-otp"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-recovery-code"]}],"description":"A grant the client may use at the token endpoint. An empty `grant_types` list allows every grant except `password`, which is always opt-in."},"uniqueItems":true,"description":"Grants this client may use at the token endpoint. Empty allows every grant except `password`, which must be listed to be used."},"response_types":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string"},"application_type":{"type":"string"},"software_id":{"type":"string","nullable":true},"software_version":{"type":"string","nullable":true},"frontchannel_logout_uri":{"type":"string","nullable":true},"frontchannel_logout_session_required":{"type":"boolean"},"backchannel_logout_uri":{"type":"string","nullable":true},"backchannel_logout_session_required":{"type":"boolean"},"login_methods":{"anyOf":[{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},{"type":"null"}]},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"mfa_policy":{"anyOf":[{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},{"type":"null"}]},"recovery_channels":{"anyOf":[{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},{"type":"null"}]},"login_flow":{"anyOf":[{"type":"string"},{"type":"null"}]},"default_audience":{"type":"string","minLength":1,"description":"Audience a client_credentials token request gets when it sends none. An explicit `audience` wins. `null` removes it.","nullable":true},"management_scopes":{"type":"array","items":{"type":"string"},"uniqueItems":true,"description":"Management API scopes (e.g. `read:rolemembers`) this client may obtain with client_credentials for the audience `faable:management:<account_id>`. A token request gets the requested scopes that are in this list, or the whole list when it asks for none. Empty = the client cannot obtain Management API tokens. Replaces the whole list; `[]` removes all. Only scopes your own token holds can be added."},"saml_sp":{"anyOf":[{"type":"object","required":["enabled","entity_id","acs_url"],"properties":{"enabled":{"type":"boolean","description":"Whether this client answers SAML requests. Off keeps the configuration but every /saml route of the client returns 404."},"entity_id":{"type":"string","minLength":1,"maxLength":1024,"description":"The Service Provider's Entity ID. Its AuthnRequests must carry it as Issuer, and every assertion is restricted to it as Audience."},"acs_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The Service Provider's Assertion Consumer Service URL (HTTP-POST binding), https. The only URL a response is ever posted to: an AuthnRequest naming another one is refused."},"nameid_format":{"enum":["emailAddress","persistent","unspecified"],"description":"`emailAddress` (default) sends the email as NameID; `persistent` sends the stable user id; `unspecified` sends the email when the user has one, the user id otherwise."},"attribute_mapping":{"type":"object","description":"SAML attribute name → user field. Omit for the default (email, name, given_name, family_name). Custom ID token claims set by post-login actions are added as attributes too.","additionalProperties":{"anyOf":[{"type":"string","enum":["user_id"]},{"type":"string","enum":["email"]},{"type":"string","enum":["email_verified"]},{"type":"string","enum":["name"]},{"type":"string","enum":["given_name"]},{"type":"string","enum":["family_name"]},{"type":"string","enum":["nickname"]},{"type":"string","enum":["picture"]},{"type":"string","enum":["phone_number"]}]}},"sign_response":{"type":"boolean","description":"Also sign the Response envelope, not only the Assertion. Default true; a few SPs reject a doubly-signed message."}},"additionalProperties":false},{"type":"null"}]},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Free-form client metadata. Replaces the whole object — send the full merged value, not a partial delta. Omit it to leave the metadata untouched."}},"description":"Partial update for a Client. Only the supplied fields are modified. `client_id` and `client_secret` are not editable through this endpoint to prevent accidental rotation; use a dedicated endpoint when secret rotation is added.","additionalProperties":false}}},"description":"Partial update for a Client. Only the supplied fields are modified. `client_id` and `client_secret` are not editable through this endpoint to prevent accidental rotation; use a dedicated endpoint when secret rotation is added."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"client_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Client","content":{"application/json":{"schema":{"type":"object","required":["id","name","description","client_id","client_secret","callbacks","logout_urls","web_origins","refresh_token","account","createdAt"],"properties":{"id":{"type":"string","description":"Client ID"},"name":{"type":"string"},"description":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string"},"callbacks":{"type":"array","items":{"type":"string"}},"logout_urls":{"type":"array","items":{"type":"string"}},"web_origins":{"type":"array","items":{"type":"string"},"description":"Origins allowed to make cross-origin (CORS) requests to the token and passwordless endpoints. Empty = any origin."},"refresh_token":{"type":"object","required":["expiration_mode","infinite_token_lifetime","token_lifetime"],"properties":{"expiration_mode":{"enum":["expire","not-expire"]},"infinite_token_lifetime":{"type":"boolean"},"token_lifetime":{"type":"number"}}},"access_token":{"type":"object","properties":{"token_lifetime":{"type":"number","minimum":60,"description":"Access token lifetime in seconds for tokens issued to this client. A resolved audience (Api) token_lifetime takes precedence. Omit to use the tenant default (24h)."}}},"client_uri":{"type":"string","nullable":true},"logo_uri":{"type":"string","nullable":true},"tos_uri":{"type":"string","nullable":true},"policy_uri":{"type":"string","nullable":true},"initiate_login_uri":{"type":"string","description":"The URL in the application that starts a login. Where the hosted login screen sends people who open an expired or shared login link.","nullable":true},"contacts":{"type":"array","items":{"type":"string"}},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string"},"consent_required":{"type":"boolean","description":"When true, /authorize never issues a code in silence for this client: the End-User approves it on the consent screen first. Set on clients the tenant did not create (Client ID Metadata Documents, dynamic registration)."},"metadata_document_url":{"type":"string","description":"For a client identified by a Client ID Metadata Document: the URL (also its client_id) its metadata was fetched from."},"registered_dynamically":{"type":"boolean"},"application_type":{"type":"string"},"software_id":{"type":"string","nullable":true},"software_version":{"type":"string","nullable":true},"frontchannel_logout_uri":{"type":"string","nullable":true},"frontchannel_logout_session_required":{"type":"boolean"},"backchannel_logout_uri":{"type":"string","nullable":true},"backchannel_logout_session_required":{"type":"boolean"},"login_methods":{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"mfa_policy":{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},"recovery_channels":{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},"login_flow":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A login flow bound to this client (`loginflow_xxx`), overriding the account's. Absent = inherit."},"default_audience":{"type":"string","description":"Audience used when a client_credentials token request sends none (e.g. `faable:management:<account_id>`). An explicit `audience` in the request wins. Absent = `${iss}/userinfo`.","nullable":true},"management_scopes":{"type":"array","items":{"type":"string"},"description":"Management API scopes (e.g. `read:rolemembers`) this client may obtain with client_credentials for the audience `faable:management:<account_id>`. A token request gets the requested scopes that are in this list, or the whole list when it asks for none. Empty = the client cannot obtain Management API tokens."},"saml_sp":{"type":"object","required":["enabled","entity_id","acs_url"],"properties":{"enabled":{"type":"boolean","description":"Whether this client answers SAML requests. Off keeps the configuration but every /saml route of the client returns 404."},"entity_id":{"type":"string","minLength":1,"maxLength":1024,"description":"The Service Provider's Entity ID. Its AuthnRequests must carry it as Issuer, and every assertion is restricted to it as Audience."},"acs_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The Service Provider's Assertion Consumer Service URL (HTTP-POST binding), https. The only URL a response is ever posted to: an AuthnRequest naming another one is refused."},"nameid_format":{"enum":["emailAddress","persistent","unspecified"],"description":"`emailAddress` (default) sends the email as NameID; `persistent` sends the stable user id; `unspecified` sends the email when the user has one, the user id otherwise."},"attribute_mapping":{"type":"object","description":"SAML attribute name → user field. Omit for the default (email, name, given_name, family_name). Custom ID token claims set by post-login actions are added as attributes too.","additionalProperties":{"anyOf":[{"type":"string","enum":["user_id"]},{"type":"string","enum":["email"]},{"type":"string","enum":["email_verified"]},{"type":"string","enum":["name"]},{"type":"string","enum":["given_name"]},{"type":"string","enum":["family_name"]},{"type":"string","enum":["nickname"]},{"type":"string","enum":["picture"]},{"type":"string","enum":["phone_number"]}]}},"sign_response":{"type":"boolean","description":"Also sign the Response envelope, not only the Assertion. Default true; a few SPs reject a doubly-signed message."}},"additionalProperties":false,"nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ClientMetadata","default":{}},"createdAt":{"type":"string","description":"Client creation date"},"updatedAt":{"type":"string","description":"Client updated date"}},"description":"Client"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"client/delete","summary":"Delete Client","tags":["client"],"description":"Delete Client","parameters":[{"schema":{"type":"string"},"in":"path","name":"client_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Client","content":{"application/json":{"schema":{"type":"object","required":["id","name","description","client_id","client_secret","callbacks","logout_urls","web_origins","refresh_token","account","createdAt"],"properties":{"id":{"type":"string","description":"Client ID"},"name":{"type":"string"},"description":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string"},"callbacks":{"type":"array","items":{"type":"string"}},"logout_urls":{"type":"array","items":{"type":"string"}},"web_origins":{"type":"array","items":{"type":"string"},"description":"Origins allowed to make cross-origin (CORS) requests to the token and passwordless endpoints. Empty = any origin."},"refresh_token":{"type":"object","required":["expiration_mode","infinite_token_lifetime","token_lifetime"],"properties":{"expiration_mode":{"enum":["expire","not-expire"]},"infinite_token_lifetime":{"type":"boolean"},"token_lifetime":{"type":"number"}}},"access_token":{"type":"object","properties":{"token_lifetime":{"type":"number","minimum":60,"description":"Access token lifetime in seconds for tokens issued to this client. A resolved audience (Api) token_lifetime takes precedence. Omit to use the tenant default (24h)."}}},"client_uri":{"type":"string","nullable":true},"logo_uri":{"type":"string","nullable":true},"tos_uri":{"type":"string","nullable":true},"policy_uri":{"type":"string","nullable":true},"initiate_login_uri":{"type":"string","description":"The URL in the application that starts a login. Where the hosted login screen sends people who open an expired or shared login link.","nullable":true},"contacts":{"type":"array","items":{"type":"string"}},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string"},"consent_required":{"type":"boolean","description":"When true, /authorize never issues a code in silence for this client: the End-User approves it on the consent screen first. Set on clients the tenant did not create (Client ID Metadata Documents, dynamic registration)."},"metadata_document_url":{"type":"string","description":"For a client identified by a Client ID Metadata Document: the URL (also its client_id) its metadata was fetched from."},"registered_dynamically":{"type":"boolean"},"application_type":{"type":"string"},"software_id":{"type":"string","nullable":true},"software_version":{"type":"string","nullable":true},"frontchannel_logout_uri":{"type":"string","nullable":true},"frontchannel_logout_session_required":{"type":"boolean"},"backchannel_logout_uri":{"type":"string","nullable":true},"backchannel_logout_session_required":{"type":"boolean"},"login_methods":{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"mfa_policy":{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},"recovery_channels":{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},"login_flow":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A login flow bound to this client (`loginflow_xxx`), overriding the account's. Absent = inherit."},"default_audience":{"type":"string","description":"Audience used when a client_credentials token request sends none (e.g. `faable:management:<account_id>`). An explicit `audience` in the request wins. Absent = `${iss}/userinfo`.","nullable":true},"management_scopes":{"type":"array","items":{"type":"string"},"description":"Management API scopes (e.g. `read:rolemembers`) this client may obtain with client_credentials for the audience `faable:management:<account_id>`. A token request gets the requested scopes that are in this list, or the whole list when it asks for none. Empty = the client cannot obtain Management API tokens."},"saml_sp":{"type":"object","required":["enabled","entity_id","acs_url"],"properties":{"enabled":{"type":"boolean","description":"Whether this client answers SAML requests. Off keeps the configuration but every /saml route of the client returns 404."},"entity_id":{"type":"string","minLength":1,"maxLength":1024,"description":"The Service Provider's Entity ID. Its AuthnRequests must carry it as Issuer, and every assertion is restricted to it as Audience."},"acs_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The Service Provider's Assertion Consumer Service URL (HTTP-POST binding), https. The only URL a response is ever posted to: an AuthnRequest naming another one is refused."},"nameid_format":{"enum":["emailAddress","persistent","unspecified"],"description":"`emailAddress` (default) sends the email as NameID; `persistent` sends the stable user id; `unspecified` sends the email when the user has one, the user id otherwise."},"attribute_mapping":{"type":"object","description":"SAML attribute name → user field. Omit for the default (email, name, given_name, family_name). Custom ID token claims set by post-login actions are added as attributes too.","additionalProperties":{"anyOf":[{"type":"string","enum":["user_id"]},{"type":"string","enum":["email"]},{"type":"string","enum":["email_verified"]},{"type":"string","enum":["name"]},{"type":"string","enum":["given_name"]},{"type":"string","enum":["family_name"]},{"type":"string","enum":["nickname"]},{"type":"string","enum":["picture"]},{"type":"string","enum":["phone_number"]}]}},"sign_response":{"type":"boolean","description":"Also sign the Response envelope, not only the Assertion. Default true; a few SPs reject a doubly-signed message."}},"additionalProperties":false,"nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ClientMetadata","default":{}},"createdAt":{"type":"string","description":"Client creation date"},"updatedAt":{"type":"string","description":"Client updated date"}},"description":"Client"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/client/{client_id}/rotate-secret":{"post":{"operationId":"client/rotateSecret","summary":"Rotate a Client's secret","tags":["client"],"description":"Generates a fresh `client_secret` for the given Client and persists it. The previous secret is invalidated immediately — there is no grace period. Returns the full Client; the dashboard must surface the new secret to the admin once. Existing client_credentials / authorization_code integrations using the old secret will start failing at the token endpoint immediately after this call.","parameters":[{"schema":{"type":"string"},"in":"path","name":"client_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Client","content":{"application/json":{"schema":{"type":"object","required":["id","name","description","client_id","client_secret","callbacks","logout_urls","web_origins","refresh_token","account","createdAt"],"properties":{"id":{"type":"string","description":"Client ID"},"name":{"type":"string"},"description":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string"},"callbacks":{"type":"array","items":{"type":"string"}},"logout_urls":{"type":"array","items":{"type":"string"}},"web_origins":{"type":"array","items":{"type":"string"},"description":"Origins allowed to make cross-origin (CORS) requests to the token and passwordless endpoints. Empty = any origin."},"refresh_token":{"type":"object","required":["expiration_mode","infinite_token_lifetime","token_lifetime"],"properties":{"expiration_mode":{"enum":["expire","not-expire"]},"infinite_token_lifetime":{"type":"boolean"},"token_lifetime":{"type":"number"}}},"access_token":{"type":"object","properties":{"token_lifetime":{"type":"number","minimum":60,"description":"Access token lifetime in seconds for tokens issued to this client. A resolved audience (Api) token_lifetime takes precedence. Omit to use the tenant default (24h)."}}},"client_uri":{"type":"string","nullable":true},"logo_uri":{"type":"string","nullable":true},"tos_uri":{"type":"string","nullable":true},"policy_uri":{"type":"string","nullable":true},"initiate_login_uri":{"type":"string","description":"The URL in the application that starts a login. Where the hosted login screen sends people who open an expired or shared login link.","nullable":true},"contacts":{"type":"array","items":{"type":"string"}},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string"},"consent_required":{"type":"boolean","description":"When true, /authorize never issues a code in silence for this client: the End-User approves it on the consent screen first. Set on clients the tenant did not create (Client ID Metadata Documents, dynamic registration)."},"metadata_document_url":{"type":"string","description":"For a client identified by a Client ID Metadata Document: the URL (also its client_id) its metadata was fetched from."},"registered_dynamically":{"type":"boolean"},"application_type":{"type":"string"},"software_id":{"type":"string","nullable":true},"software_version":{"type":"string","nullable":true},"frontchannel_logout_uri":{"type":"string","nullable":true},"frontchannel_logout_session_required":{"type":"boolean"},"backchannel_logout_uri":{"type":"string","nullable":true},"backchannel_logout_session_required":{"type":"boolean"},"login_methods":{"type":"object","properties":{"order":{"type":"array","items":{"type":"string"},"description":"Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."},"disabled_connections":{"type":"array","items":{"type":"string"},"description":"Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."},"passkey_login_enabled":{"type":"boolean","description":"Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."},"identifier_first":{"type":"boolean","description":"Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."},"remember_last_method":{"type":"boolean","description":"Surface the method the returning user chose last time. Defaults to false."},"passkey_promotion":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["offer"]}],"description":"Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."},"passkey_promotion_snooze_days":{"type":"integer","minimum":0,"maximum":365,"description":"Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."},"passkey_promotion_max_prompts":{"type":"integer","minimum":0,"maximum":10,"description":"How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."},"remember_me":{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]}],"description":"Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."},"remember_me_days":{"type":"integer","minimum":1,"maximum":365,"description":"How long a session lasts when the user ticked \"Remember me\". Defaults to 30."},"theme":{"anyOf":[{"type":"string","enum":["system"]},{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}],"description":"Colour theme of the hosted login screens. `system` (the default): the visitor's OS decides, and the visitor can switch it from the screen. `light` / `dark`: forced, and the switch is hidden — for applications whose own pages are only one of the two. A `theme` query parameter on `/authorize` overrides this for that login."}},"additionalProperties":false,"description":"Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."},"profile_fields":{"type":"array","items":{"type":"object","required":["key","type"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9_]{1,40}$","description":"The field key, as it is stored in `user_metadata[\"faable.com/fields\"]`."},"type":{"anyOf":[{"type":"string","enum":["string"]},{"type":"string","enum":["enum"]}],"description":"`string`: free text up to `max_length`. `enum`: one of `options`."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":100,"description":"The accepted values when `type` is `enum`."},"max_length":{"type":"integer","minimum":1,"maximum":1000,"description":"Longest accepted value for a `string` field. Default 200."}},"additionalProperties":false},"maxItems":20,"description":"Profile fields the user may write on themselves with `PATCH /me` (`fields`), stored in `user_metadata[\"faable.com/fields\"]`. On a Client it replaces the account's list entirely; absent = inherit.","nullable":true},"mfa_policy":{"type":"object","properties":{"mode":{"anyOf":[{"anyOf":[{"type":"string","enum":["off"]},{"type":"string","enum":["optional"]},{"type":"string","enum":["required"]}]}],"description":"`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."},"allowed_factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."},"remember_device_days":{"type":"integer","minimum":0,"maximum":365,"description":"How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."}},"additionalProperties":false,"description":"Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."},"recovery_channels":{"type":"object","properties":{"enabled":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."},"default":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."},"visible":{"type":"array","items":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"description":"Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."}},"additionalProperties":false,"description":"Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."},"login_flow":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A login flow bound to this client (`loginflow_xxx`), overriding the account's. Absent = inherit."},"default_audience":{"type":"string","description":"Audience used when a client_credentials token request sends none (e.g. `faable:management:<account_id>`). An explicit `audience` in the request wins. Absent = `${iss}/userinfo`.","nullable":true},"management_scopes":{"type":"array","items":{"type":"string"},"description":"Management API scopes (e.g. `read:rolemembers`) this client may obtain with client_credentials for the audience `faable:management:<account_id>`. A token request gets the requested scopes that are in this list, or the whole list when it asks for none. Empty = the client cannot obtain Management API tokens."},"saml_sp":{"type":"object","required":["enabled","entity_id","acs_url"],"properties":{"enabled":{"type":"boolean","description":"Whether this client answers SAML requests. Off keeps the configuration but every /saml route of the client returns 404."},"entity_id":{"type":"string","minLength":1,"maxLength":1024,"description":"The Service Provider's Entity ID. Its AuthnRequests must carry it as Issuer, and every assertion is restricted to it as Audience."},"acs_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The Service Provider's Assertion Consumer Service URL (HTTP-POST binding), https. The only URL a response is ever posted to: an AuthnRequest naming another one is refused."},"nameid_format":{"enum":["emailAddress","persistent","unspecified"],"description":"`emailAddress` (default) sends the email as NameID; `persistent` sends the stable user id; `unspecified` sends the email when the user has one, the user id otherwise."},"attribute_mapping":{"type":"object","description":"SAML attribute name → user field. Omit for the default (email, name, given_name, family_name). Custom ID token claims set by post-login actions are added as attributes too.","additionalProperties":{"anyOf":[{"type":"string","enum":["user_id"]},{"type":"string","enum":["email"]},{"type":"string","enum":["email_verified"]},{"type":"string","enum":["name"]},{"type":"string","enum":["given_name"]},{"type":"string","enum":["family_name"]},{"type":"string","enum":["nickname"]},{"type":"string","enum":["picture"]},{"type":"string","enum":["phone_number"]}]}},"sign_response":{"type":"boolean","description":"Also sign the Response envelope, not only the Assertion. Default true; a few SPs reject a doubly-signed message."}},"additionalProperties":false,"nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ClientMetadata","default":{}},"createdAt":{"type":"string","description":"Client creation date"},"updatedAt":{"type":"string","description":"Client updated date"}},"description":"Client"}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`client_mismatch` — The client belongs to a different account.\n\n`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["client_mismatch","unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`client_not_found` — No client with that `client_id`.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["client_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user":{"get":{"operationId":"user/list","summary":"List Users","tags":["user"],"description":"List Users","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"string"},"in":"query","name":"email","required":false,"description":"Exact match on `email`. Equivalent to `?query=email:<value>`."},{"schema":{"type":"string"},"in":"query","name":"country_iso","required":false,"description":"Exact match on `country_iso`. Equivalent to `?query=country_iso:<value>`."},{"schema":{"type":"string"},"in":"query","name":"locale","required":false,"description":"Exact match on `locale`. Equivalent to `?query=locale:<value>`."},{"schema":{"type":"string"},"in":"query","name":"last_ip","required":false,"description":"Exact match on `last_ip`. Equivalent to `?query=last_ip:<value>`."},{"schema":{"type":"string"},"in":"query","name":"phone","required":false,"description":"Exact match on `phone`. Equivalent to `?query=phone:<value>`."},{"schema":{"type":"string"},"in":"query","name":"name","required":false,"description":"Exact match on `name`. Equivalent to `?query=name:<value>`."},{"schema":{"type":"string"},"in":"query","name":"email_verified","required":false,"description":"Exact match on `email_verified`. Equivalent to `?query=email_verified:<value>`."},{"schema":{"type":"string"},"in":"query","name":"suspended","required":false,"description":"Exact match on `suspended`. Equivalent to `?query=suspended:<value>`."},{"schema":{"type":"string"},"in":"query","name":"last_login_since","required":false,"description":"Exact match on `last_login_since`. Equivalent to `?query=last_login_since:<value>`."},{"schema":{"type":"string"},"in":"query","name":"last_login_until","required":false,"description":"Exact match on `last_login_until`. Equivalent to `?query=last_login_until:<value>`."},{"schema":{"type":"string"},"in":"query","name":"created_since","required":false,"description":"Exact match on `created_since`. Equivalent to `?query=created_since:<value>`."},{"schema":{"type":"string"},"in":"query","name":"created_until","required":false,"description":"Exact match on `created_until`. Equivalent to `?query=created_until:<value>`."},{"schema":{"type":"string"},"in":"query","name":"sort","required":false,"description":"Sort by one field: `<field>` ascending, `-<field>` descending. One of: createdAt, last_login, logins_count. Rows with no value in that field are left out. Default: `-createdAt`."},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"query","name":"q","required":false,"description":"Full-text search across: `name`, `email`, `phone`."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/User"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"user/create","summary":"Create User","tags":["user"],"description":"Create User","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The user's full name."},"given_name":{"type":"string","description":"The user's given name."},"family_name":{"type":"string","description":"The user's family name."},"email":{"type":"string","description":"The user's email."},"phone":{"type":"string","description":"contact phone number"},"birth_date":{"type":"string","description":"The user's birth date"},"locale":{"type":"string","description":"The user's default locale lang."},"picture":{"type":"string","description":"A URI pointing to the user's picture."},"user_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"User Metadata"},"app_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"App Metadata"}},"description":"UserCreate","additionalProperties":false}}},"description":"UserCreate"},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"User","content":{"application/json":{"schema":{"type":"object","required":["id","email_verified","phone_verified","logins_count","user_metadata","app_metadata","account","createdAt"],"properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User name","nullable":true},"username":{"type":"string","description":"unique username for this user","nullable":true},"given_name":{"type":"string","description":"Given name","nullable":true},"family_name":{"type":"string","description":"Family name","nullable":true},"middle_name":{"type":"string","description":"Middle name (OIDC §5.1)","nullable":true},"nickname":{"type":"string","description":"Casual name. Distinct from given_name (e.g. \"Mike\" vs \"Michael\"). OIDC §5.1","nullable":true},"email":{"type":"string","description":"User email","nullable":true},"email_verified":{"type":"boolean","description":"true if email is verified","default":false},"email_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `email_verified` was last set. `manual` — admin flipped the flag via `POST /user/:id`. `verification_flow` — user clicked the verification link. `passwordless_otp` — user completed passwordless OTP login. `team_invite` — user clicked a team invitation link. `email_change` — user confirmed a self-service email change. `federated` — verified by the external IdP on OAuth callback. `password_reset` — user completed the recovery link that was emailed to them (the `email` channel only). `import` — the user arrived verified from another provider through `POST /user/import` (asserted by whoever ran the migration). `null` — the field was cleared (admin un-verified the email)."},"email_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `email_verified` was last flipped to true.","nullable":true},"email_change_locked_at":{"type":"string","description":"ISO 8601 timestamp of the user's last verified email change. When set, OAuth callbacks will not overwrite `email`/`email_verified` from the federated provider — the manually-chosen email wins.","nullable":true},"email_bounced_at":{"type":"string","description":"ISO 8601 timestamp of the last Postmark hard bounce / spam complaint for this address. When set, the email is treated as undeliverable and no further emails are sent to the user until the address changes.","nullable":true},"welcome_sent_at":{"type":"string","description":"ISO 8601 timestamp of the built-in welcome email send. Set once by the platform; guarantees at most one welcome per user.","nullable":true},"imported_at":{"type":"string","description":"ISO 8601 timestamp of when the user was created by `POST /user/import`. Imported users get none of the built-in new-user emails (verification, welcome, `user.created` notifications); `user.created` webhooks still fire and carry this field.","nullable":true},"passkey_prompted_at":{"type":"string","description":"ISO 8601 timestamp of the last time the hosted \"create a passkey\" offer was shown to this user after a login. Drives the snooze in `login_methods.passkey_promotion_snooze_days`.","nullable":true},"passkey_prompt_count":{"type":"integer","description":"How many times the passkey offer has been shown to this user. Capped by `login_methods.passkey_promotion_max_prompts`; reset by an administrator through `POST /user/:id/passkey-prompt/reset`."},"passkey_prompt_dismissed_at":{"type":"string","description":"ISO 8601 timestamp of the last time the user chose \"Not now\" on the passkey offer, as opposed to closing the tab.","nullable":true},"phone":{"type":"string","description":"contact phone number","nullable":true},"phone_e164":{"type":"boolean","description":"Whether `phone` is a valid E.164 number. Calculated on read, never stored: new writes are rejected unless they normalise, so a `false` here is a legacy row that no SMS can reach — fix the number, or set the account default country and write it again."},"phone_verified":{"type":"boolean","description":"phone is verified"},"phone_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `phone_verified` was last set. Same enum as `email_verified_method`, plus `sms_otp` — the user typed a code sent by SMS (`POST /user/:id/verify-phone/start`)."},"phone_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `phone_verified` was last flipped to true.","nullable":true},"country_iso":{"type":"string","description":"country iso code","nullable":true},"birth_date":{"type":"string","description":"user birth_date","nullable":true},"gender":{"type":"string","description":"User gender (free-form, OIDC §5.1)","nullable":true},"zoneinfo":{"type":"string","description":"IANA time-zone name (e.g. \"Europe/Madrid\"). OIDC §5.1 — used for the `zoneinfo` claim.","nullable":true},"locale":{"type":"string","description":"user main language","nullable":true},"region":{"type":"string","description":"customer region","nullable":true},"website":{"type":"string","description":"URL of the User's personal Web page or blog (OIDC §5.1)","nullable":true},"address":{"type":"object","properties":{"formatted":{"type":"string","description":"Full mailing address, formatted for display (may contain newlines)","nullable":true},"street_address":{"type":"string","nullable":true},"locality":{"type":"string","description":"City or locality","nullable":true},"region":{"type":"string","description":"State, province, or region","nullable":true},"postal_code":{"type":"string","nullable":true},"country":{"type":"string","description":"Country name","nullable":true}},"additionalProperties":false,"nullable":true},"picture":{"type":"string","description":"User picture url","nullable":true},"logins_count":{"type":"number","description":"Total number of logins this user has performed."},"last_ip":{"type":"string","description":"Last IP address from which this user logged in","nullable":true},"last_login":{"type":"string","description":"Last date and time this user logged in (ISO_8601 format)","nullable":true},"first_language":{"type":"string","description":"Primary Accept-Language subtag seen at this user's FIRST login (e.g. \"zh\"). Written once and never updated, so a later login whose language differs is visible as `event.stats.language_changed` in actions — a cross-session signal no single request can produce.","nullable":true},"internal_metadata":{"type":"object","properties":{"last_ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}},"signup":{"type":"object","required":["at"],"properties":{"ip":{"type":"string"},"user_agent":{"type":"string"},"language":{"type":"string"},"at":{"type":"string","format":"date-time"},"ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}}}}},"readOnly":true,"description":"Computed by Faable Auth, never writable through the API. `last_ip_info`: country, network (ASN) and reputation of `last_ip`, resolved after each login. `signup`: the IP, browser and language of the request that created the user, so a user who signed up but never logged in is not a blank — with the same resolution in `ip_info`. IP geolocation by DB-IP (CC BY 4.0)."},"suspended":{"type":"boolean","description":"true while the user is suspended. A suspended user is rejected at every login flow, token grant, session use and management-API call.","default":false},"suspended_at":{"type":"string","description":"ISO 8601 timestamp of when `suspended` was last flipped to true.","nullable":true},"suspended_reason":{"type":"string","description":"Free-form reason recorded when the user was suspended (e.g. \"abuse: RCE payload\").","nullable":true},"user_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"User Metadata","default":{}},"app_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"App Metadata","default":{}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"UserMetadata","default":{}},"createdAt":{"type":"string","description":"User creation date"},"updatedAt":{"type":"string","description":"User updated date"}},"description":"User"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.\n\n`invalid_phone` — The phone number is not E.164 and could not be resolved with the account default country. Include the country code.\n\n`invalid_name` — The name contains a link. `message` says which field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error","invalid_phone","invalid_name"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}":{"get":{"operationId":"user/get","summary":"Get User","tags":["user"],"description":"Get User","parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"User","content":{"application/json":{"schema":{"type":"object","required":["id","email_verified","phone_verified","logins_count","user_metadata","app_metadata","account","createdAt"],"properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User name","nullable":true},"username":{"type":"string","description":"unique username for this user","nullable":true},"given_name":{"type":"string","description":"Given name","nullable":true},"family_name":{"type":"string","description":"Family name","nullable":true},"middle_name":{"type":"string","description":"Middle name (OIDC §5.1)","nullable":true},"nickname":{"type":"string","description":"Casual name. Distinct from given_name (e.g. \"Mike\" vs \"Michael\"). OIDC §5.1","nullable":true},"email":{"type":"string","description":"User email","nullable":true},"email_verified":{"type":"boolean","description":"true if email is verified","default":false},"email_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `email_verified` was last set. `manual` — admin flipped the flag via `POST /user/:id`. `verification_flow` — user clicked the verification link. `passwordless_otp` — user completed passwordless OTP login. `team_invite` — user clicked a team invitation link. `email_change` — user confirmed a self-service email change. `federated` — verified by the external IdP on OAuth callback. `password_reset` — user completed the recovery link that was emailed to them (the `email` channel only). `import` — the user arrived verified from another provider through `POST /user/import` (asserted by whoever ran the migration). `null` — the field was cleared (admin un-verified the email)."},"email_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `email_verified` was last flipped to true.","nullable":true},"email_change_locked_at":{"type":"string","description":"ISO 8601 timestamp of the user's last verified email change. When set, OAuth callbacks will not overwrite `email`/`email_verified` from the federated provider — the manually-chosen email wins.","nullable":true},"email_bounced_at":{"type":"string","description":"ISO 8601 timestamp of the last Postmark hard bounce / spam complaint for this address. When set, the email is treated as undeliverable and no further emails are sent to the user until the address changes.","nullable":true},"welcome_sent_at":{"type":"string","description":"ISO 8601 timestamp of the built-in welcome email send. Set once by the platform; guarantees at most one welcome per user.","nullable":true},"imported_at":{"type":"string","description":"ISO 8601 timestamp of when the user was created by `POST /user/import`. Imported users get none of the built-in new-user emails (verification, welcome, `user.created` notifications); `user.created` webhooks still fire and carry this field.","nullable":true},"passkey_prompted_at":{"type":"string","description":"ISO 8601 timestamp of the last time the hosted \"create a passkey\" offer was shown to this user after a login. Drives the snooze in `login_methods.passkey_promotion_snooze_days`.","nullable":true},"passkey_prompt_count":{"type":"integer","description":"How many times the passkey offer has been shown to this user. Capped by `login_methods.passkey_promotion_max_prompts`; reset by an administrator through `POST /user/:id/passkey-prompt/reset`."},"passkey_prompt_dismissed_at":{"type":"string","description":"ISO 8601 timestamp of the last time the user chose \"Not now\" on the passkey offer, as opposed to closing the tab.","nullable":true},"phone":{"type":"string","description":"contact phone number","nullable":true},"phone_e164":{"type":"boolean","description":"Whether `phone` is a valid E.164 number. Calculated on read, never stored: new writes are rejected unless they normalise, so a `false` here is a legacy row that no SMS can reach — fix the number, or set the account default country and write it again."},"phone_verified":{"type":"boolean","description":"phone is verified"},"phone_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `phone_verified` was last set. Same enum as `email_verified_method`, plus `sms_otp` — the user typed a code sent by SMS (`POST /user/:id/verify-phone/start`)."},"phone_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `phone_verified` was last flipped to true.","nullable":true},"country_iso":{"type":"string","description":"country iso code","nullable":true},"birth_date":{"type":"string","description":"user birth_date","nullable":true},"gender":{"type":"string","description":"User gender (free-form, OIDC §5.1)","nullable":true},"zoneinfo":{"type":"string","description":"IANA time-zone name (e.g. \"Europe/Madrid\"). OIDC §5.1 — used for the `zoneinfo` claim.","nullable":true},"locale":{"type":"string","description":"user main language","nullable":true},"region":{"type":"string","description":"customer region","nullable":true},"website":{"type":"string","description":"URL of the User's personal Web page or blog (OIDC §5.1)","nullable":true},"address":{"type":"object","properties":{"formatted":{"type":"string","description":"Full mailing address, formatted for display (may contain newlines)","nullable":true},"street_address":{"type":"string","nullable":true},"locality":{"type":"string","description":"City or locality","nullable":true},"region":{"type":"string","description":"State, province, or region","nullable":true},"postal_code":{"type":"string","nullable":true},"country":{"type":"string","description":"Country name","nullable":true}},"additionalProperties":false,"nullable":true},"picture":{"type":"string","description":"User picture url","nullable":true},"logins_count":{"type":"number","description":"Total number of logins this user has performed."},"last_ip":{"type":"string","description":"Last IP address from which this user logged in","nullable":true},"last_login":{"type":"string","description":"Last date and time this user logged in (ISO_8601 format)","nullable":true},"first_language":{"type":"string","description":"Primary Accept-Language subtag seen at this user's FIRST login (e.g. \"zh\"). Written once and never updated, so a later login whose language differs is visible as `event.stats.language_changed` in actions — a cross-session signal no single request can produce.","nullable":true},"internal_metadata":{"type":"object","properties":{"last_ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}},"signup":{"type":"object","required":["at"],"properties":{"ip":{"type":"string"},"user_agent":{"type":"string"},"language":{"type":"string"},"at":{"type":"string","format":"date-time"},"ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}}}}},"readOnly":true,"description":"Computed by Faable Auth, never writable through the API. `last_ip_info`: country, network (ASN) and reputation of `last_ip`, resolved after each login. `signup`: the IP, browser and language of the request that created the user, so a user who signed up but never logged in is not a blank — with the same resolution in `ip_info`. IP geolocation by DB-IP (CC BY 4.0)."},"suspended":{"type":"boolean","description":"true while the user is suspended. A suspended user is rejected at every login flow, token grant, session use and management-API call.","default":false},"suspended_at":{"type":"string","description":"ISO 8601 timestamp of when `suspended` was last flipped to true.","nullable":true},"suspended_reason":{"type":"string","description":"Free-form reason recorded when the user was suspended (e.g. \"abuse: RCE payload\").","nullable":true},"user_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"User Metadata","default":{}},"app_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"App Metadata","default":{}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"UserMetadata","default":{}},"createdAt":{"type":"string","description":"User creation date"},"updatedAt":{"type":"string","description":"User updated date"}},"description":"User"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"user/update","summary":"Update User","tags":["user"],"description":"Update User","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"name"},"given_name":{"type":"string","description":"given name"},"family_name":{"type":"string","description":"family name"},"email":{"type":"string","description":"Contact email. Changing the email to a new value automatically resets `email_verified` to false (and clears `email_verified_method` / `email_verified_at`) unless the same patch sets `email_verified` explicitly. The override is the documented way to import a pre-verified user from another IdP."},"email_verified":{"type":"boolean","description":"Flip the email verification flag. Setting `true` marks the email as verified by the admin (`email_verified_method=manual`). Setting `false` clears the verification metadata."},"phone":{"type":"string","description":"Contact phone number. Changing it to a new value automatically resets `phone_verified` to false (and clears `phone_verified_method` / `phone_verified_at`) unless the same patch sets `phone_verified` explicitly."},"phone_verified":{"type":"boolean","description":"Flip the phone verification flag. Setting `true` marks the phone as verified by the admin (`phone_verified_method=manual`)."},"suspended":{"type":"boolean","description":"Suspend (`true`) or reinstate (`false`) the user. While suspended, every login flow, token grant, session use and management-API call is rejected. Setting `true` auto-stamps `suspended_at`; setting `false` clears `suspended_at` / `suspended_reason`."},"suspended_reason":{"type":"string","maxLength":512,"description":"Free-form reason for the suspension (e.g. \"abuse: RCE payload\"). Stored verbatim for audit purposes."},"country_iso":{"type":"string","description":"user country as iso string"},"birth_date":{"type":"string","description":"user birth_date"},"locale":{"type":"string","description":"user main language","nullable":true},"picture":{"type":"string","description":"User picture url"},"user_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"User defined metadata"},"app_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"App defined metadata"}},"description":"UserUpdate","additionalProperties":false}}},"description":"UserUpdate"},"parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"User","content":{"application/json":{"schema":{"type":"object","required":["id","email_verified","phone_verified","logins_count","user_metadata","app_metadata","account","createdAt"],"properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User name","nullable":true},"username":{"type":"string","description":"unique username for this user","nullable":true},"given_name":{"type":"string","description":"Given name","nullable":true},"family_name":{"type":"string","description":"Family name","nullable":true},"middle_name":{"type":"string","description":"Middle name (OIDC §5.1)","nullable":true},"nickname":{"type":"string","description":"Casual name. Distinct from given_name (e.g. \"Mike\" vs \"Michael\"). OIDC §5.1","nullable":true},"email":{"type":"string","description":"User email","nullable":true},"email_verified":{"type":"boolean","description":"true if email is verified","default":false},"email_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `email_verified` was last set. `manual` — admin flipped the flag via `POST /user/:id`. `verification_flow` — user clicked the verification link. `passwordless_otp` — user completed passwordless OTP login. `team_invite` — user clicked a team invitation link. `email_change` — user confirmed a self-service email change. `federated` — verified by the external IdP on OAuth callback. `password_reset` — user completed the recovery link that was emailed to them (the `email` channel only). `import` — the user arrived verified from another provider through `POST /user/import` (asserted by whoever ran the migration). `null` — the field was cleared (admin un-verified the email)."},"email_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `email_verified` was last flipped to true.","nullable":true},"email_change_locked_at":{"type":"string","description":"ISO 8601 timestamp of the user's last verified email change. When set, OAuth callbacks will not overwrite `email`/`email_verified` from the federated provider — the manually-chosen email wins.","nullable":true},"email_bounced_at":{"type":"string","description":"ISO 8601 timestamp of the last Postmark hard bounce / spam complaint for this address. When set, the email is treated as undeliverable and no further emails are sent to the user until the address changes.","nullable":true},"welcome_sent_at":{"type":"string","description":"ISO 8601 timestamp of the built-in welcome email send. Set once by the platform; guarantees at most one welcome per user.","nullable":true},"imported_at":{"type":"string","description":"ISO 8601 timestamp of when the user was created by `POST /user/import`. Imported users get none of the built-in new-user emails (verification, welcome, `user.created` notifications); `user.created` webhooks still fire and carry this field.","nullable":true},"passkey_prompted_at":{"type":"string","description":"ISO 8601 timestamp of the last time the hosted \"create a passkey\" offer was shown to this user after a login. Drives the snooze in `login_methods.passkey_promotion_snooze_days`.","nullable":true},"passkey_prompt_count":{"type":"integer","description":"How many times the passkey offer has been shown to this user. Capped by `login_methods.passkey_promotion_max_prompts`; reset by an administrator through `POST /user/:id/passkey-prompt/reset`."},"passkey_prompt_dismissed_at":{"type":"string","description":"ISO 8601 timestamp of the last time the user chose \"Not now\" on the passkey offer, as opposed to closing the tab.","nullable":true},"phone":{"type":"string","description":"contact phone number","nullable":true},"phone_e164":{"type":"boolean","description":"Whether `phone` is a valid E.164 number. Calculated on read, never stored: new writes are rejected unless they normalise, so a `false` here is a legacy row that no SMS can reach — fix the number, or set the account default country and write it again."},"phone_verified":{"type":"boolean","description":"phone is verified"},"phone_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `phone_verified` was last set. Same enum as `email_verified_method`, plus `sms_otp` — the user typed a code sent by SMS (`POST /user/:id/verify-phone/start`)."},"phone_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `phone_verified` was last flipped to true.","nullable":true},"country_iso":{"type":"string","description":"country iso code","nullable":true},"birth_date":{"type":"string","description":"user birth_date","nullable":true},"gender":{"type":"string","description":"User gender (free-form, OIDC §5.1)","nullable":true},"zoneinfo":{"type":"string","description":"IANA time-zone name (e.g. \"Europe/Madrid\"). OIDC §5.1 — used for the `zoneinfo` claim.","nullable":true},"locale":{"type":"string","description":"user main language","nullable":true},"region":{"type":"string","description":"customer region","nullable":true},"website":{"type":"string","description":"URL of the User's personal Web page or blog (OIDC §5.1)","nullable":true},"address":{"type":"object","properties":{"formatted":{"type":"string","description":"Full mailing address, formatted for display (may contain newlines)","nullable":true},"street_address":{"type":"string","nullable":true},"locality":{"type":"string","description":"City or locality","nullable":true},"region":{"type":"string","description":"State, province, or region","nullable":true},"postal_code":{"type":"string","nullable":true},"country":{"type":"string","description":"Country name","nullable":true}},"additionalProperties":false,"nullable":true},"picture":{"type":"string","description":"User picture url","nullable":true},"logins_count":{"type":"number","description":"Total number of logins this user has performed."},"last_ip":{"type":"string","description":"Last IP address from which this user logged in","nullable":true},"last_login":{"type":"string","description":"Last date and time this user logged in (ISO_8601 format)","nullable":true},"first_language":{"type":"string","description":"Primary Accept-Language subtag seen at this user's FIRST login (e.g. \"zh\"). Written once and never updated, so a later login whose language differs is visible as `event.stats.language_changed` in actions — a cross-session signal no single request can produce.","nullable":true},"internal_metadata":{"type":"object","properties":{"last_ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}},"signup":{"type":"object","required":["at"],"properties":{"ip":{"type":"string"},"user_agent":{"type":"string"},"language":{"type":"string"},"at":{"type":"string","format":"date-time"},"ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}}}}},"readOnly":true,"description":"Computed by Faable Auth, never writable through the API. `last_ip_info`: country, network (ASN) and reputation of `last_ip`, resolved after each login. `signup`: the IP, browser and language of the request that created the user, so a user who signed up but never logged in is not a blank — with the same resolution in `ip_info`. IP geolocation by DB-IP (CC BY 4.0)."},"suspended":{"type":"boolean","description":"true while the user is suspended. A suspended user is rejected at every login flow, token grant, session use and management-API call.","default":false},"suspended_at":{"type":"string","description":"ISO 8601 timestamp of when `suspended` was last flipped to true.","nullable":true},"suspended_reason":{"type":"string","description":"Free-form reason recorded when the user was suspended (e.g. \"abuse: RCE payload\").","nullable":true},"user_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"User Metadata","default":{}},"app_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"App Metadata","default":{}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"UserMetadata","default":{}},"createdAt":{"type":"string","description":"User creation date"},"updatedAt":{"type":"string","description":"User updated date"}},"description":"User"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.\n\n`invalid_phone` — The phone number is not E.164 and could not be resolved with the account default country. Include the country code.\n\n`invalid_name` — The name contains a link. `message` says which field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","already_exists","validation_error","invalid_phone","invalid_name"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"user/delete","summary":"Delete User","tags":["user"],"description":"Delete User","parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"User","content":{"application/json":{"schema":{"type":"object","required":["id","email_verified","phone_verified","logins_count","user_metadata","app_metadata","account","createdAt"],"properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User name","nullable":true},"username":{"type":"string","description":"unique username for this user","nullable":true},"given_name":{"type":"string","description":"Given name","nullable":true},"family_name":{"type":"string","description":"Family name","nullable":true},"middle_name":{"type":"string","description":"Middle name (OIDC §5.1)","nullable":true},"nickname":{"type":"string","description":"Casual name. Distinct from given_name (e.g. \"Mike\" vs \"Michael\"). OIDC §5.1","nullable":true},"email":{"type":"string","description":"User email","nullable":true},"email_verified":{"type":"boolean","description":"true if email is verified","default":false},"email_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `email_verified` was last set. `manual` — admin flipped the flag via `POST /user/:id`. `verification_flow` — user clicked the verification link. `passwordless_otp` — user completed passwordless OTP login. `team_invite` — user clicked a team invitation link. `email_change` — user confirmed a self-service email change. `federated` — verified by the external IdP on OAuth callback. `password_reset` — user completed the recovery link that was emailed to them (the `email` channel only). `import` — the user arrived verified from another provider through `POST /user/import` (asserted by whoever ran the migration). `null` — the field was cleared (admin un-verified the email)."},"email_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `email_verified` was last flipped to true.","nullable":true},"email_change_locked_at":{"type":"string","description":"ISO 8601 timestamp of the user's last verified email change. When set, OAuth callbacks will not overwrite `email`/`email_verified` from the federated provider — the manually-chosen email wins.","nullable":true},"email_bounced_at":{"type":"string","description":"ISO 8601 timestamp of the last Postmark hard bounce / spam complaint for this address. When set, the email is treated as undeliverable and no further emails are sent to the user until the address changes.","nullable":true},"welcome_sent_at":{"type":"string","description":"ISO 8601 timestamp of the built-in welcome email send. Set once by the platform; guarantees at most one welcome per user.","nullable":true},"imported_at":{"type":"string","description":"ISO 8601 timestamp of when the user was created by `POST /user/import`. Imported users get none of the built-in new-user emails (verification, welcome, `user.created` notifications); `user.created` webhooks still fire and carry this field.","nullable":true},"passkey_prompted_at":{"type":"string","description":"ISO 8601 timestamp of the last time the hosted \"create a passkey\" offer was shown to this user after a login. Drives the snooze in `login_methods.passkey_promotion_snooze_days`.","nullable":true},"passkey_prompt_count":{"type":"integer","description":"How many times the passkey offer has been shown to this user. Capped by `login_methods.passkey_promotion_max_prompts`; reset by an administrator through `POST /user/:id/passkey-prompt/reset`."},"passkey_prompt_dismissed_at":{"type":"string","description":"ISO 8601 timestamp of the last time the user chose \"Not now\" on the passkey offer, as opposed to closing the tab.","nullable":true},"phone":{"type":"string","description":"contact phone number","nullable":true},"phone_e164":{"type":"boolean","description":"Whether `phone` is a valid E.164 number. Calculated on read, never stored: new writes are rejected unless they normalise, so a `false` here is a legacy row that no SMS can reach — fix the number, or set the account default country and write it again."},"phone_verified":{"type":"boolean","description":"phone is verified"},"phone_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `phone_verified` was last set. Same enum as `email_verified_method`, plus `sms_otp` — the user typed a code sent by SMS (`POST /user/:id/verify-phone/start`)."},"phone_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `phone_verified` was last flipped to true.","nullable":true},"country_iso":{"type":"string","description":"country iso code","nullable":true},"birth_date":{"type":"string","description":"user birth_date","nullable":true},"gender":{"type":"string","description":"User gender (free-form, OIDC §5.1)","nullable":true},"zoneinfo":{"type":"string","description":"IANA time-zone name (e.g. \"Europe/Madrid\"). OIDC §5.1 — used for the `zoneinfo` claim.","nullable":true},"locale":{"type":"string","description":"user main language","nullable":true},"region":{"type":"string","description":"customer region","nullable":true},"website":{"type":"string","description":"URL of the User's personal Web page or blog (OIDC §5.1)","nullable":true},"address":{"type":"object","properties":{"formatted":{"type":"string","description":"Full mailing address, formatted for display (may contain newlines)","nullable":true},"street_address":{"type":"string","nullable":true},"locality":{"type":"string","description":"City or locality","nullable":true},"region":{"type":"string","description":"State, province, or region","nullable":true},"postal_code":{"type":"string","nullable":true},"country":{"type":"string","description":"Country name","nullable":true}},"additionalProperties":false,"nullable":true},"picture":{"type":"string","description":"User picture url","nullable":true},"logins_count":{"type":"number","description":"Total number of logins this user has performed."},"last_ip":{"type":"string","description":"Last IP address from which this user logged in","nullable":true},"last_login":{"type":"string","description":"Last date and time this user logged in (ISO_8601 format)","nullable":true},"first_language":{"type":"string","description":"Primary Accept-Language subtag seen at this user's FIRST login (e.g. \"zh\"). Written once and never updated, so a later login whose language differs is visible as `event.stats.language_changed` in actions — a cross-session signal no single request can produce.","nullable":true},"internal_metadata":{"type":"object","properties":{"last_ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}},"signup":{"type":"object","required":["at"],"properties":{"ip":{"type":"string"},"user_agent":{"type":"string"},"language":{"type":"string"},"at":{"type":"string","format":"date-time"},"ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}}}}},"readOnly":true,"description":"Computed by Faable Auth, never writable through the API. `last_ip_info`: country, network (ASN) and reputation of `last_ip`, resolved after each login. `signup`: the IP, browser and language of the request that created the user, so a user who signed up but never logged in is not a blank — with the same resolution in `ip_info`. IP geolocation by DB-IP (CC BY 4.0)."},"suspended":{"type":"boolean","description":"true while the user is suspended. A suspended user is rejected at every login flow, token grant, session use and management-API call.","default":false},"suspended_at":{"type":"string","description":"ISO 8601 timestamp of when `suspended` was last flipped to true.","nullable":true},"suspended_reason":{"type":"string","description":"Free-form reason recorded when the user was suspended (e.g. \"abuse: RCE payload\").","nullable":true},"user_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"User Metadata","default":{}},"app_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"App Metadata","default":{}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"UserMetadata","default":{}},"createdAt":{"type":"string","description":"User creation date"},"updatedAt":{"type":"string","description":"User updated date"}},"description":"User"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}/change-email":{"post":{"operationId":"user/changeEmail","summary":"Start the user's email change flow","tags":["user"],"description":"Initiates the email-change flow for the given user. Validates the new email, ensures it is not already in use in the tenant, revokes any pending change for the same user, creates a Ticket (`type='change_email'`), and sends a verification email to the new address. Re-authentication is not required — an active session/Bearer is enough. The change does NOT take effect until the user clicks the link in the email and confirms via `/change-email-confirm`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["new_email"],"properties":{"new_email":{"type":"string","description":"The email address the user wants to switch to."},"verification_mode":{"anyOf":[{"type":"string","enum":["new_only"]},{"type":"string","enum":["old_and_new"]}],"description":"Override the account-wide policy. Can only raise the bar: passing `old_and_new` forces double verification; `new_only` is ignored if the account requires `old_and_new`."},"redirect_uri":{"type":"string","description":"Where to send the user after they click the link in the verification email. The status of the operation is appended as a `?status=applied|pending_old` query param so the calling app can render the right screen. When omitted, the user lands on the auth host's `/flow/email-change-done` fallback."}},"additionalProperties":false,"description":"Payload for `POST /user/:user_id/change-email`. Triggers the verification flow: a Ticket is created and an email is sent to the new address with a confirmation link."}}},"description":"Payload for `POST /user/:user_id/change-email`. Triggers the verification flow: a Ticket is created and an email is sent to the new address with a confirmation link."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["status","ticket_id","verification_mode"],"properties":{"status":{"type":"string","enum":["verification_sent"]},"ticket_id":{"type":"string"},"verification_mode":{"anyOf":[{"type":"string","enum":["new_only"]},{"type":"string","enum":["old_and_new"]}]}}}}}},"400":{"description":"`invalid_email` — The email address is not valid.\n\n`same_email` — The new email is the same as the current one.\n\n`user_has_no_email` — The operation needs an email but the user has none.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_email","same_email","user_has_no_email","validation_error"]}}}]}}}},"401":{"description":"`invalid_token` — The bearer token is missing, malformed, expired or has no subject.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_token"]}}}]}}}},"403":{"description":"`not_owner` — The caller may only perform this operation on their own user.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_owner"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"409":{"description":"`email_taken` — Another user in this account already uses that email.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["email_taken"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/change-email-verify":{"get":{"operationId":"user_change_email_verify","summary":"Verify a change-email ticket and apply the change","tags":["user"],"description":"Entry point for the link sent to the new email. Consumes the ticket, applies the email swap (in `new_only` mode) or issues a secondary verification ticket to the old email (in `old_and_new` mode, first step). Redirects to the `redirect_uri` supplied at flow start with `?status=applied|pending_old` appended; falls back to `/flow/email-change-done` on the auth host when no `redirect_uri` was given.","parameters":[{"schema":{"type":"string"},"in":"query","name":"ticket","required":true}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_ticket` — The ticket does not exist or is not the kind this endpoint accepts.\n\n`ticket_expired` — The ticket is past its expiry.\n\n`ticket_used` — The ticket was already consumed.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_ticket","ticket_expired","ticket_used","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}/verify-email/start":{"post":{"operationId":"user/verifyEmailStart","summary":"Start the user's email verification flow","tags":["user"],"description":"Initiates the email-verification flow for the given user. Creates a `verify_email` Ticket and queues the verification email. Idempotent — returns `{status:'already_verified'}` without emitting a ticket if the user is already verified. Re-authentication is not required: an active session/Bearer is enough.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"redirect_uri":{"type":"string","description":"Where to send the user after they click the link. The status of the operation is appended as `?status=verified|already_verified|invalid_link|expired` so the calling app can render the right screen. When omitted, the user lands on the auth host's `/flow/verify-email-done` fallback."}},"additionalProperties":false,"description":"Optional payload for `POST /user/:user_id/verify-email/start`. Trigger a verification email send for the given user."}}},"description":"Optional payload for `POST /user/:user_id/verify-email/start`. Trigger a verification email send for the given user."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"anyOf":[{"type":"string","enum":["verification_sent"]},{"type":"string","enum":["already_verified"]}]},"ticket_id":{"type":"string"}}}}}},"400":{"description":"`user_has_no_email` — The operation needs an email but the user has none.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["user_has_no_email","validation_error"]}}}]}}}},"401":{"description":"`invalid_token` — The bearer token is missing, malformed, expired or has no subject.\n\n`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_token","unauthorized"]}}}]}}}},"403":{"description":"`not_owner` — The caller may only perform this operation on their own user.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_owner"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/verify-email":{"get":{"operationId":"user_verify_email_confirm","summary":"Verify a verify-email ticket and mark the user verified","tags":["user"],"description":"Entry point for the link sent to the user. Consumes the ticket, sets `email_verified=true` with `email_verified_method='verification_flow'`. Redirects to the `redirect_uri` supplied at start with `?status=verified|already_verified` appended; falls back to `/flow/verify-email-done` on the auth host when no `redirect_uri` was given. Invalid/expired tickets redirect to the same fallback with `?status=invalid_link` or `?status=expired`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"ticket","required":true}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_ticket` — The ticket does not exist or is not the kind this endpoint accepts.\n\n`ticket_expired` — The ticket is past its expiry.\n\n`ticket_used` — The ticket was already consumed.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_ticket","ticket_expired","ticket_used","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}/password-setup":{"post":{"operationId":"user/passwordSetup","summary":"Send a password setup / reset email to the user","tags":["user"],"description":"Ensures the user has a database credential (provisioning a password-less one when missing, e.g. for users created via the management API) and creates a `changepassword` ticket, sending the standard password reset email. The link lands on the tenant's auth domain. Idempotent on the credential; each call emits a fresh ticket.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"connection":{"type":"string","description":"Optional connection_name to disambiguate when the tenant has more than one database connection. Defaults to the tenant database connection."},"channel":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"How to deliver it: `email` (the link) or `sms` / `whatsapp` (a 6-digit code to the verified phone on file). Falls back to the tenant default when not available for this user; the response says which channel was used."}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["status","credential_id","ticket_id","channel"],"properties":{"status":{"type":"string","enum":["sent"]},"credential_id":{"type":"string"},"ticket_id":{"type":"string"},"channel":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}}}}}},"400":{"description":"`invalid_client` — The `client_id` in the request does not name a client of this account.\n\n`invalid_connection` — The connection does not exist, is disabled, or is not of the type this flow needs.\n\n`no_database_connection` — The tenant has no database connection, so there is nowhere to store a password. Create one and retry.\n\n`user_has_no_email` — The operation needs an email but the user has none.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_client","invalid_connection","no_database_connection","user_has_no_email","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}/tickets":{"get":{"operationId":"user/listTickets","summary":"List the tickets issued for a user","tags":["user"],"description":"Returns the password-reset / verification / invitation tickets issued for this user, newest first, each with its status and — while it is still usable — the link that was emailed. Intended for handing a recovery link over by another channel when the email does not reach the person. Requires `update:credentials`: the link grants the same account access that setting a password does, and each reveal is recorded in the audit log.","parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["results"],"properties":{"results":{"type":"array","items":{"type":"object","required":["id","type","status","email","createdAt","expires_at","ttl","channel"],"properties":{"id":{"type":"string"},"type":{"type":"string"},"status":{"anyOf":[{"type":"string","enum":["active"]},{"type":"string","enum":["expired"]},{"type":"string","enum":["used"]}]},"email":{"type":"string"},"createdAt":{"type":"string"},"expires_at":{"type":"string"},"ttl":{"type":"number"},"channel":{"type":"string","description":"How this ticket reaches the person: `email` (the link), `sms` / `whatsapp` (a code), `factor`. Older tickets are `email`."},"link":{"type":"string","description":"The link from the ticket email. Present only while the ticket is usable — a dead link is noise, not a recovery path."}}}}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}/tickets/{ticket_id}/revoke":{"post":{"operationId":"user/revokeTicket","summary":"Revoke a ticket issued for a user","tags":["user"],"description":"Marks the ticket as consumed so its link stops working immediately. The counterpart of listing links: once a link has been copied out of the dashboard it can end up in the wrong chat window, and the TTL alone is too slow an answer.","parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true},{"schema":{"type":"string"},"in":"path","name":"ticket_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["status","id"],"properties":{"status":{"anyOf":[{"type":"string","enum":["revoked"]},{"type":"string","enum":["already_used"]}]},"id":{"type":"string"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`ticket_not_found` — No ticket with that id belongs to this user.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["ticket_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}/verify-phone/start":{"post":{"operationId":"user/verifyPhoneStart","summary":"Send a verification code to the user's phone","tags":["user"],"description":"Sends a 6-digit code by SMS to the user's phone (optionally setting a new phone first) and returns an opaque `state` to confirm it with. Same authorization as `verify-email/start`: the user themself (session or bearer), an admin of another tenant, or a machine token. Counts against the tenant's monthly SMS allowance. Fails with 409 `sms_unavailable` when no SMS can go out (no provider, plan does not allow it, allowance exhausted).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string","description":"Phone to verify. When given, it replaces the user's phone (normalised to E.164 with the account's `default_country_iso` as fallback) before the code is sent. When omitted, the code goes to the phone already on the user."}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["status","destination_masked","state","expires_in"],"properties":{"status":{"type":"string","enum":["sent"]},"destination_masked":{"type":"string"},"state":{"type":"string","description":"Opaque handle to confirm with. It is what lets a tenant backend start the verification (M2M) and send the person to `/flow/verify-phone?state=…` without a session."},"expires_in":{"type":"integer"}}}}}},"400":{"description":"`invalid_phone` — The phone number is not E.164 and could not be resolved with the account default country. Include the country code.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_phone","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"409":{"description":"`sms_failed` — The SMS provider rejected the message.\n\n`sms_unavailable` — This account cannot send SMS right now. The suffix says why: `sms_unavailable:plan`, `:quota`, `:provider`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["sms_failed","sms_unavailable"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/verify-phone/confirm":{"post":{"operationId":"user/verifyPhoneConfirm","summary":"Confirm a phone verification code","tags":["user"],"description":"Marks the phone verified (`phone_verified_method: sms_otp`) when the code matches the pending verification identified by `state`. Five wrong codes burn the `state`; start again. No session needed: the `state` plus the code are the proof.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state","code"],"properties":{"state":{"type":"string"},"code":{"type":"string","minLength":4,"maxLength":12}},"additionalProperties":false}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["status","user_id"],"properties":{"status":{"type":"string","enum":["verified"]},"user_id":{"type":"string"}}}}}},"400":{"description":"`invalid_code` — The verification code is wrong, expired or already used.\n\n`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`phone_changed` — The phone on the user changed after the code was sent. Start again.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_code","invalid_state","phone_changed","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/verify-phone/pending":{"get":{"operationId":"user/verifyPhonePending","summary":"What the verification screen should say","tags":["user"],"description":"The masked destination of a pending verification and how long its code is still good for. The screen needs this to say \"we sent a code to ···· 628\": the `destination_masked` of `start` goes to whoever called it, not to the person typing the code.","parameters":[{"schema":{"type":"string"},"in":"query","name":"state","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["destination_masked","expires_in","can_change"],"properties":{"destination_masked":{"type":"string"},"expires_in":{"type":"integer"},"can_change":{"type":"boolean","description":"Whether the screen may offer \"this is not my number\". False once the phone is verified: from then on the `state` must not be able to point it somewhere else."},"default_country":{"type":"string","description":"The account's default country (ISO 3166-1 alpha-2), so the screen can preselect the right dial code when the person corrects their number."}}}}}},"400":{"description":"`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_state","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/verify-phone/restart":{"post":{"operationId":"user/verifyPhoneRestart","summary":"Correct the number and send a new code","tags":["user"],"description":"Replaces the phone of a pending verification and sends a fresh code, returning a new `state`; the old one stops working. No session needed — the `state` is the proof — which is why it refuses with 409 `phone_already_verified` once the person has a verified phone: from that point a leaked `state` must not be able to move the number. Counts against the monthly SMS allowance like `start`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state","phone"],"properties":{"state":{"type":"string"},"phone":{"type":"string","minLength":4}},"additionalProperties":false}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["status","destination_masked","state","expires_in"],"properties":{"status":{"type":"string","enum":["sent"]},"destination_masked":{"type":"string"},"state":{"type":"string","description":"Opaque handle to confirm with. It is what lets a tenant backend start the verification (M2M) and send the person to `/flow/verify-phone?state=…` without a session."},"expires_in":{"type":"integer"}}}}}},"400":{"description":"`invalid_phone` — The phone number is not E.164 and could not be resolved with the account default country. Include the country code.\n\n`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_phone","invalid_state","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"409":{"description":"`phone_already_verified` — The user already has a verified phone; the number cannot be swapped from the screen.\n\n`sms_failed` — The SMS provider rejected the message.\n\n`sms_unavailable` — This account cannot send SMS right now. The suffix says why: `sms_unavailable:plan`, `:quota`, `:provider`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["phone_already_verified","sms_failed","sms_unavailable"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}/factors":{"get":{"operationId":"user/factors","summary":"List a user's second factors","tags":["user"],"description":"Management view of the security methods a user has enrolled. Carries no secret material — TOTP seeds and recovery-code hashes are redacted by the store, so this endpoint cannot be used to impersonate the user.","parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminFactor"}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}/factors/{factor_id}":{"delete":{"operationId":"user/deleteFactor","summary":"Remove one of a user's second factors","tags":["user"],"description":"The support path out of a lockout: with the factor gone, a `required` policy sends the user through enrolment on their next login instead of refusing it.","parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true},{"schema":{"type":"string"},"in":"path","name":"factor_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"boolean"}}}}}},"400":{"description":"`factor_not_found` — No factor with that id belongs to this user.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["factor_not_found","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}/passkey-prompt/reset":{"post":{"operationId":"user/resetPasskeyPrompt","summary":"Reset a user's passkey offer counter","tags":["user"],"description":"The post-login \"create a passkey\" offer stops after `login_methods.passkey_promotion_max_prompts` times and snoozes between them. This clears both, so the user is offered a passkey again on their next login — for \"why does it keep asking\" and \"please ask me again\" alike.","parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["reset"],"properties":{"reset":{"type":"boolean"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}/ip":{"get":{"operationId":"user/ip","summary":"Insight into a user's last IP","tags":["user"],"description":"Location and network (country, ASN), reputation signals and usage of the user's `last_ip`. Empty when the user has never logged in.","parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserIpInsight"}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/import":{"post":{"operationId":"user/import","summary":"Import users, with their password hashes","tags":["user"],"description":"Creates up to 500 users per request, for migrations from another provider. Each row can carry a `password_hash` exported from that provider (bcrypt, scrypt, Firebase scrypt, PBKDF2, argon2, WordPress 6.8+ `$wp$2y$`): the user signs in with their existing password and the hash is upgraded to argon2id on that first login. Fast digests (MD5, SHA-1, SHA-2) and low costs are refused per row with `weak_password_hash`. Rows are matched by email, so the same file can be sent twice: existing users are `skipped` unless `update_existing`. Needs `create:users`, and `update:credentials` only with `update_existing`. A bad row never fails the batch: each row gets its own `status` and `error`. Imported users get none of the built-in new-user emails. The response never contains a hash, and neither does the audit log.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["users"],"properties":{"users":{"type":"array","items":{"type":"object","required":["email"],"properties":{"email":{"type":"string","minLength":3,"description":"The user's email."},"email_verified":{"type":"boolean","description":"The email was verified at the previous provider. Recorded with `email_verified_method: import`."},"name":{"type":"string"},"given_name":{"type":"string"},"family_name":{"type":"string"},"phone":{"type":"string"},"birth_date":{"type":"string"},"locale":{"type":"string"},"picture":{"type":"string"},"user_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"User Metadata"},"app_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"App Metadata"},"password_hash":{"anyOf":[{"type":"string","minLength":1,"maxLength":1024,"description":"A self-describing hash: bcrypt (`$2a$`/`$2b$`/`$2y$`), argon2 PHC (`$argon2id$…`), scrypt PHC (`$scrypt$ln=…`), PBKDF2 (`$pbkdf2-sha256$i=…` or Django `pbkdf2_sha256$…`), WordPress 6.8+ (`$wp$2y$…`, as stored in `wp_users.user_pass`)."},{"type":"object","required":["algorithm","hash"],"properties":{"algorithm":{"type":"string","description":"`bcrypt`, `argon2id`, `argon2i`, `argon2d`, `scrypt`, `pbkdf2-sha256`, `pbkdf2-sha512`, `firebase-scrypt` or `wordpress`."},"hash":{"type":"string","minLength":1,"maxLength":1024,"description":"The hash, or its base64 digest when `salt` is given."},"salt":{"type":"string","maxLength":512,"description":"Base64 salt."},"params":{"type":"object","description":"Algorithm parameters: `iterations` (pbkdf2); `N`, `r`, `p` (scrypt); `signer_key`, `salt_separator`, `rounds`, `mem_cost` (firebase-scrypt).","additionalProperties":{"anyOf":[{"type":"number"},{"type":"string","maxLength":512}]}}},"additionalProperties":false}],"description":"A password hash exported from another provider. Verified on the first login and upgraded to argon2id. Fast digests (MD5, SHA-1, SHA-2) and low costs are refused with `weak_password_hash`."},"identities":{"type":"array","items":{"type":"object","required":["connection","identity_id"],"properties":{"connection":{"type":"string","description":"connection_name of the social/enterprise connection."},"identity_id":{"type":"string","description":"The user's id at the provider (GitHub id, Google `sub`…), so their first social login finds this user instead of creating a new one."},"profile_data":{"type":"object","description":"Provider profile, stored as-is.","additionalProperties":{}}},"additionalProperties":false},"maxItems":20}},"additionalProperties":false},"minItems":1,"maxItems":500},"connection":{"type":"string","description":"connection_name of the database connection the password hashes go to. Defaults to the tenant database connection."},"update_existing":{"type":"boolean","description":"Set the password hash and identities of users that already exist (matched by email) instead of skipping them. Their profile is never overwritten.","default":false},"dry_run":{"type":"boolean","description":"Validate every row and resolve every match without writing anything.","default":false}},"additionalProperties":false}}}},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["summary","results"],"properties":{"summary":{"type":"object","required":["total","created","updated","skipped","error"],"properties":{"total":{"type":"number"},"created":{"type":"number"},"updated":{"type":"number"},"skipped":{"type":"number"},"error":{"type":"number"}}},"results":{"type":"array","items":{"type":"object","required":["index","email","status"],"properties":{"index":{"type":"number","description":"Position of the row in `users`."},"email":{"type":"string"},"status":{"anyOf":[{"type":"string","enum":["created"]},{"type":"string","enum":["updated"]},{"type":"string","enum":["skipped"]},{"type":"string","enum":["error"]}]},"user_id":{"type":"string"},"skipped_identities":{"type":"array","items":{"type":"string"},"description":"Connections named in `identities` that this tenant does not have (e.g. Apple from a Firebase export). The user was imported without them."},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/export":{"get":{"operationId":"user/export","summary":"Export users, optionally with their password hashes","tags":["user"],"description":"Pages through every user of the tenant, 500 at a time, in the row shape `POST /user/import` takes. With `include_hashes` each row carries its `password_hash` (needs the `read:password_hashes` scope, is audited as `credentials.exported` and notifies its subscribers). Provider access and refresh tokens of linked identities are never exported. Available on every plan.","parameters":[{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"`next_cursor` of the previous page."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":500},"in":"query","name":"limit","required":false},{"schema":{"type":"string"},"in":"query","name":"connection","required":false,"description":"connection_name of the database connection whose password hashes are exported. Defaults to the tenant database connection."},{"schema":{"type":"boolean","default":false},"in":"query","name":"include_hashes","required":false,"description":"Include `password_hash`. Needs the `read:password_hashes` scope, is written to the audit log and notifies the `credentials.exported` subscribers."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["users","next_cursor","connection"],"properties":{"users":{"type":"array","items":{"type":"object","required":["user_id","email","email_verified","password_hash_status"],"properties":{"user_id":{"type":"string"},"email":{"anyOf":[{"type":"string"},{"type":"null"}]},"email_verified":{"type":"boolean"},"name":{"type":"string"},"given_name":{"type":"string"},"family_name":{"type":"string"},"phone":{"type":"string"},"birth_date":{"type":"string"},"locale":{"type":"string"},"picture":{"type":"string"},"user_metadata":{"type":"object","additionalProperties":{}},"app_metadata":{"type":"object","additionalProperties":{}},"password_hash_status":{"anyOf":[{"type":"string","enum":["set"]},{"type":"string","enum":["not_set"]},{"type":"string","enum":["no_credential"]},{"type":"string","enum":["legacy_unexportable"]}],"description":"`legacy_unexportable`: the password is still stored in a pre-2026 format that is not exported; it upgrades on the next login."},"password_hash":{"type":"string","description":"Self-describing hash (argon2id PHC for passwords set on Faable; the original format for imported ones not yet upgraded). Only with `include_hashes`."},"identities":{"type":"array","items":{"type":"object","required":["connection","identity_id"],"properties":{"connection":{"type":"string"},"identity_id":{"type":"string"},"profile_data":{"type":"object","additionalProperties":{}}}}}}}},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}]},"connection":{"anyOf":[{"type":"string"},{"type":"null"}]}}}}}},"400":{"description":"`invalid_connection` — The connection does not exist, is disabled, or is not of the type this flow needs.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_connection","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/identity":{"get":{"operationId":"identity/list","summary":"List Identitys","tags":["identity"],"description":"List Identitys","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"string"},"in":"query","name":"user","required":false,"description":"Exact match on `user`. Equivalent to `?query=user:<value>`."},{"schema":{"type":"string"},"in":"query","name":"connection","required":false,"description":"Exact match on `connection`. Equivalent to `?query=connection:<value>`."},{"schema":{"type":"string"},"in":"query","name":"identity_id","required":false,"description":"Exact match on `identity_id`. Equivalent to `?query=identity_id:<value>`."},{"schema":{"type":"string"},"in":"query","name":"provider","required":false,"description":"Exact match on `provider`. Equivalent to `?query=provider:<value>`."},{"schema":{"type":"string"},"in":"query","name":"created_at","required":false,"description":"Exact match on `created_at`. Equivalent to `?query=created_at:<value>`."},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"array","items":{"type":"string","enum":["user","connection"],"description":"Allowed expand paths on `identity`: `user`, `connection`."},"maxItems":2},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `?expand=user`)."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Identity"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"identity/create","summary":"Create Identity","tags":["identity"],"description":"Create Identity","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user","identity_id","connection","profile_data"],"properties":{"user":{"type":"string"},"identity_id":{"type":"string"},"connection":{"type":"string"},"access_token":{"type":"string"},"access_token_secret":{"type":"string"},"refresh_token":{"type":"string"},"profile_data":{}}}}}},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Identity","content":{"application/json":{"schema":{"type":"object","required":["id","connection","user","identity_id","profile_data","account","createdAt"],"properties":{"id":{"type":"string","description":"Identity ID"},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"identity_id":{"type":"string"},"access_token":{"type":"string"},"access_token_secret":{"type":"string"},"refresh_token":{"type":"string"},"access_token_expires_at":{"type":"string","description":"ISO 8601 timestamp when the stored provider `access_token` expires. Stamped from the provider `expires_in` on (re-)authorization and refresh. Absent when the provider issues non-expiring tokens."},"profile_data":{},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"IdentityMetadata","default":{}},"createdAt":{"type":"string","description":"Identity creation date"},"updatedAt":{"type":"string","description":"Identity updated date"}},"description":"Identity"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/identity/{identity_id}":{"get":{"operationId":"identity/get","summary":"Get Identity","tags":["identity"],"description":"Get Identity","parameters":[{"schema":{"type":"array","items":{"type":"string","enum":["user","connection"],"description":"Allowed expand paths on `identity`: `user`, `connection`."},"maxItems":2},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `?expand=user`)."},{"schema":{"type":"string"},"in":"path","name":"identity_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Identity","content":{"application/json":{"schema":{"type":"object","required":["id","connection","user","identity_id","profile_data","account","createdAt"],"properties":{"id":{"type":"string","description":"Identity ID"},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"identity_id":{"type":"string"},"access_token":{"type":"string"},"access_token_secret":{"type":"string"},"refresh_token":{"type":"string"},"access_token_expires_at":{"type":"string","description":"ISO 8601 timestamp when the stored provider `access_token` expires. Stamped from the provider `expires_in` on (re-)authorization and refresh. Absent when the provider issues non-expiring tokens."},"profile_data":{},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"IdentityMetadata","default":{}},"createdAt":{"type":"string","description":"Identity creation date"},"updatedAt":{"type":"string","description":"Identity updated date"}},"description":"Identity"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"identity/delete","summary":"Delete Identity","tags":["identity"],"description":"Delete Identity","parameters":[{"schema":{"type":"string"},"in":"path","name":"identity_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Identity","content":{"application/json":{"schema":{"type":"object","required":["id","connection","user","identity_id","profile_data","account","createdAt"],"properties":{"id":{"type":"string","description":"Identity ID"},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"identity_id":{"type":"string"},"access_token":{"type":"string"},"access_token_secret":{"type":"string"},"refresh_token":{"type":"string"},"access_token_expires_at":{"type":"string","description":"ISO 8601 timestamp when the stored provider `access_token` expires. Stamped from the provider `expires_in` on (re-)authorization and refresh. Absent when the provider issues non-expiring tokens."},"profile_data":{},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"IdentityMetadata","default":{}},"createdAt":{"type":"string","description":"Identity creation date"},"updatedAt":{"type":"string","description":"Identity updated date"}},"description":"Identity"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/identity/{identity_id}/provider-token":{"get":{"operationId":"identity/getProviderToken","summary":"Get a guaranteed-valid provider access_token","tags":["identity"],"description":"Returns the identity's upstream provider `access_token`, refreshing it transparently via the stored `refresh_token` when it is (about to be) expired. Pass `?fresh=true` to force a refresh. Fails with 400 when the token is expired and no `refresh_token` is available (the user must re-authorize).","parameters":[{"schema":{"type":"boolean"},"in":"query","name":"fresh","required":false,"description":"Force a refresh against the provider even when the stored token still looks valid."},{"schema":{"type":"string"},"in":"path","name":"identity_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["access_token","refreshed"],"properties":{"access_token":{"type":"string"},"access_token_expires_at":{"type":"string"},"refreshed":{"type":"boolean"}}}}}},"400":{"description":"`connection_misconfigured` — The connection is missing settings needed to talk to its provider.\n\n`no_provider_token` — The identity holds no provider access token.\n\n`no_refresh_token` — The identity holds no provider refresh token, so it cannot be refreshed.\n\n`provider_error` — The upstream identity provider returned an error. The suffix is the provider’s own code, when it sent one: `provider_error:bad_refresh_token` (the user must re-authorize), `provider_error:incorrect_client_credentials` (our configuration, the user can do nothing). `details.provider_error_description` carries the provider’s sentence.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["connection_misconfigured","no_provider_token","no_refresh_token","provider_error","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/credentials":{"get":{"operationId":"credentials/list","summary":"List Credentialss","tags":["credentials"],"description":"List Credentialss","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"string"},"in":"query","name":"user","required":false,"description":"Exact match on `user`. Equivalent to `?query=user:<value>`."},{"schema":{"type":"string"},"in":"query","name":"connection","required":false,"description":"Exact match on `connection`. Equivalent to `?query=connection:<value>`."},{"schema":{"type":"string"},"in":"query","name":"email","required":false,"description":"Exact match on `email`. Equivalent to `?query=email:<value>`."},{"schema":{"type":"string"},"in":"query","name":"username","required":false,"description":"Exact match on `username`. Equivalent to `?query=username:<value>`."},{"schema":{"type":"string"},"in":"query","name":"created_at","required":false,"description":"Exact match on `created_at`. Equivalent to `?query=created_at:<value>`."},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"array","items":{"type":"string","enum":["user","connection"],"description":"Allowed expand paths on `credentials`: `user`, `connection`."},"maxItems":2},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `?expand=user`)."},{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"query","name":"q","required":false,"description":"Full-text search across: `email`, `username`."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Credential"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"credentials/create","summary":"Create Credentials","tags":["credentials"],"description":"Create Credentials","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user","connection"],"properties":{"user":{"type":"string"},"connection":{"type":"string"},"email":{"type":"string"},"username":{"type":"string"}},"additionalProperties":false}}}},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Credential","content":{"application/json":{"schema":{"type":"object","required":["id","connection","user","account","createdAt"],"properties":{"id":{"type":"string","description":"Credential ID"},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"email":{"type":"string","description":"Email login identifier (unique within the connection)."},"username":{"type":"string","description":"Username login identifier (unique within the connection)."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"CredentialMetadata","default":{}},"createdAt":{"type":"string","description":"Credential creation date"},"updatedAt":{"type":"string","description":"Credential updated date"}},"description":"Credential"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/credentials/{credentials_id}":{"get":{"operationId":"credentials/get","summary":"Get Credentials","tags":["credentials"],"description":"Get Credentials","parameters":[{"schema":{"type":"array","items":{"type":"string","enum":["user","connection"],"description":"Allowed expand paths on `credentials`: `user`, `connection`."},"maxItems":2},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `?expand=user`)."},{"schema":{"type":"string"},"in":"path","name":"credentials_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Credential","content":{"application/json":{"schema":{"type":"object","required":["id","connection","user","account","createdAt"],"properties":{"id":{"type":"string","description":"Credential ID"},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"email":{"type":"string","description":"Email login identifier (unique within the connection)."},"username":{"type":"string","description":"Username login identifier (unique within the connection)."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"CredentialMetadata","default":{}},"createdAt":{"type":"string","description":"Credential creation date"},"updatedAt":{"type":"string","description":"Credential updated date"}},"description":"Credential"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"credentials/update","summary":"Update Credentials","tags":["credentials"],"description":"Update Credentials","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"username":{"type":"string"}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"credentials_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Credential","content":{"application/json":{"schema":{"type":"object","required":["id","connection","user","account","createdAt"],"properties":{"id":{"type":"string","description":"Credential ID"},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"email":{"type":"string","description":"Email login identifier (unique within the connection)."},"username":{"type":"string","description":"Username login identifier (unique within the connection)."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"CredentialMetadata","default":{}},"createdAt":{"type":"string","description":"Credential creation date"},"updatedAt":{"type":"string","description":"Credential updated date"}},"description":"Credential"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"credentials/delete","summary":"Delete Credentials","tags":["credentials"],"description":"Delete Credentials","parameters":[{"schema":{"type":"string"},"in":"path","name":"credentials_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Credential","content":{"application/json":{"schema":{"type":"object","required":["id","connection","user","account","createdAt"],"properties":{"id":{"type":"string","description":"Credential ID"},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"email":{"type":"string","description":"Email login identifier (unique within the connection)."},"username":{"type":"string","description":"Username login identifier (unique within the connection)."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"CredentialMetadata","default":{}},"createdAt":{"type":"string","description":"Credential creation date"},"updatedAt":{"type":"string","description":"Credential updated date"}},"description":"Credential"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/credentials/{credentials_id}/set-password":{"post":{"operationId":"credentials/setPassword","summary":"Set a credential's password","tags":["credentials"],"description":"Sets or rotates the password for a database credential. Send exactly one of `password` (plaintext, hashed with argon2id) or `password_hash` (a hash exported from another provider: bcrypt, scrypt, Firebase scrypt, PBKDF2 or argon2 — verified on the first login and upgraded to argon2id). A plaintext password must meet the password policy of the connection the credential belongs to (`password_too_weak` lists what is missing); an imported hash cannot be checked and is not. The previous password is invalidated immediately. A plaintext password records a `credential.password_changed` audit entry (initiated_by=admin); the confirmation email is only sent when an EXISTING password is rotated. An imported hash records `credential.password_imported` with its algorithm and sends no email. Neither the password nor the hash is ever returned or logged.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"password":{"type":"string","description":"New plaintext password (hashed on write)."},"password_hash":{"anyOf":[{"type":"string","minLength":1,"maxLength":1024,"description":"A self-describing hash: bcrypt (`$2a$`/`$2b$`/`$2y$`), argon2 PHC (`$argon2id$…`), scrypt PHC (`$scrypt$ln=…`), PBKDF2 (`$pbkdf2-sha256$i=…` or Django `pbkdf2_sha256$…`), WordPress 6.8+ (`$wp$2y$…`, as stored in `wp_users.user_pass`)."},{"type":"object","required":["algorithm","hash"],"properties":{"algorithm":{"type":"string","description":"`bcrypt`, `argon2id`, `argon2i`, `argon2d`, `scrypt`, `pbkdf2-sha256`, `pbkdf2-sha512`, `firebase-scrypt` or `wordpress`."},"hash":{"type":"string","minLength":1,"maxLength":1024,"description":"The hash, or its base64 digest when `salt` is given."},"salt":{"type":"string","maxLength":512,"description":"Base64 salt."},"params":{"type":"object","description":"Algorithm parameters: `iterations` (pbkdf2); `N`, `r`, `p` (scrypt); `signer_key`, `salt_separator`, `rounds`, `mem_cost` (firebase-scrypt).","additionalProperties":{"anyOf":[{"type":"number"},{"type":"string","maxLength":512}]}}},"additionalProperties":false}],"description":"A password hash exported from another provider. Verified on the first login and upgraded to argon2id. Fast digests (MD5, SHA-1, SHA-2) and low costs are refused with `weak_password_hash`."}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"credentials_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Credential","content":{"application/json":{"schema":{"type":"object","required":["id","connection","user","account","createdAt"],"properties":{"id":{"type":"string","description":"Credential ID"},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"email":{"type":"string","description":"Email login identifier (unique within the connection)."},"username":{"type":"string","description":"Username login identifier (unique within the connection)."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"CredentialMetadata","default":{}},"createdAt":{"type":"string","description":"Credential creation date"},"updatedAt":{"type":"string","description":"Credential updated date"}},"description":"Credential"}}}},"400":{"description":"`bad_request` — The request is malformed. `message` says what is wrong.\n\n`password_too_weak` — The password does not meet the connection policy. `message` lists each unmet rule.\n\n`invalid_password_hash` — The imported password hash cannot be read, or its cost is above what a login can afford. `message` names the field.\n\n`weak_password_hash` — The imported password hash uses a fast digest (MD5, SHA-1, SHA-2) or a cost below the minimum. `message` says which.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["bad_request","password_too_weak","invalid_password_hash","weak_password_hash","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/role/{role_id}/permissions":{"get":{"operationId":"role/permissions","summary":"List the permissions of a Role","tags":["role"],"description":"Lists the Api permissions the Role grants, each with its Api. A user gets them in tokens for an Api with `rbac` enabled when the Role is assigned to them at the account level.","parameters":[{"schema":{"type":"string"},"in":"path","name":"role_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["api","api_identifier","api_name","value"],"properties":{"api":{"type":"string","description":"Id of the Api that defines it."},"api_identifier":{"type":"string","description":"Audience of that Api: the `aud` of the tokens that carry this permission.","example":"https://api.example.com"},"api_name":{"type":"string","description":"Name of that Api."},"value":{"type":"string","description":"The permission, as it appears in the `permissions` claim.","example":"invites:write"},"description":{"type":"string","description":"The permission's description in the Api."}},"additionalProperties":false,"description":"A permission granted by a role, joined with its Api. Permissions whose Api was deleted, or that left its catalog, are not listed: they grant nothing."}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"role/add_permissions","summary":"Add permissions to a Role","tags":["role"],"description":"Grants Api permissions to a Role. Each must be in the catalog of an Api of this tenant. Permissions the Role already has are kept. Tokens issued from now on (refreshes included) carry them; tokens already issued are unchanged. Leaves an entry in the tenant's log.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["permissions"],"properties":{"permissions":{"type":"array","items":{"type":"object","required":["api","value"],"properties":{"api":{"type":"string","description":"Id of the Api that defines the permission.","example":"api_6555fd293acc2f0fac0e3452"},"value":{"type":"string","description":"The permission, as it appears in the Api's `permissions` catalog.","example":"invites:write"}},"additionalProperties":false,"description":"A permission of one Api granted by a role."},"minItems":1,"description":"Permissions to grant or revoke, each as the Api id plus the value from its catalog.","example":[{"api":"api_6555fd293acc2f0fac0e3452","value":"invites:write"}]},"actor_user_id":{"type":"string","description":"The person making the change, when your backend calls on their behalf with a machine token: it is who the tenant's log names. Must be a user of this tenant."}},"additionalProperties":false,"description":"Permissions to add to or remove from a role."}}},"description":"Permissions to add to or remove from a role."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"role_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["api","api_identifier","api_name","value"],"properties":{"api":{"type":"string","description":"Id of the Api that defines it."},"api_identifier":{"type":"string","description":"Audience of that Api: the `aud` of the tokens that carry this permission.","example":"https://api.example.com"},"api_name":{"type":"string","description":"Name of that Api."},"value":{"type":"string","description":"The permission, as it appears in the `permissions` claim.","example":"invites:write"},"description":{"type":"string","description":"The permission's description in the Api."}},"additionalProperties":false,"description":"A permission granted by a role, joined with its Api. Permissions whose Api was deleted, or that left its catalog, are not listed: they grant nothing."}}}}},"400":{"description":"`invalid_permission` — A permission is not in the catalog of its Api, or the Api is not in this account. `details.permissions` lists the ones refused.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_permission","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"role/remove_permissions","summary":"Remove permissions from a Role","tags":["role"],"description":"Revokes Api permissions from a Role. Permissions the Role does not have are ignored. Tokens already issued keep them until they expire; refreshes drop them. Leaves an entry in the tenant's log.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["permissions"],"properties":{"permissions":{"type":"array","items":{"type":"object","required":["api","value"],"properties":{"api":{"type":"string","description":"Id of the Api that defines the permission.","example":"api_6555fd293acc2f0fac0e3452"},"value":{"type":"string","description":"The permission, as it appears in the Api's `permissions` catalog.","example":"invites:write"}},"additionalProperties":false,"description":"A permission of one Api granted by a role."},"minItems":1,"description":"Permissions to grant or revoke, each as the Api id plus the value from its catalog.","example":[{"api":"api_6555fd293acc2f0fac0e3452","value":"invites:write"}]},"actor_user_id":{"type":"string","description":"The person making the change, when your backend calls on their behalf with a machine token: it is who the tenant's log names. Must be a user of this tenant."}},"additionalProperties":false,"description":"Permissions to add to or remove from a role."}}},"description":"Permissions to add to or remove from a role."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"role_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["api","api_identifier","api_name","value"],"properties":{"api":{"type":"string","description":"Id of the Api that defines it."},"api_identifier":{"type":"string","description":"Audience of that Api: the `aud` of the tokens that carry this permission.","example":"https://api.example.com"},"api_name":{"type":"string","description":"Name of that Api."},"value":{"type":"string","description":"The permission, as it appears in the `permissions` claim.","example":"invites:write"},"description":{"type":"string","description":"The permission's description in the Api."}},"additionalProperties":false,"description":"A permission granted by a role, joined with its Api. Permissions whose Api was deleted, or that left its catalog, are not listed: they grant nothing."}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/role":{"get":{"operationId":"role/list","summary":"List Roles","tags":["role"],"description":"List Roles","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"query","name":"q","required":false,"description":"Full-text search across: `name`, `description`."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Role"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"role/create","summary":"Create Role","tags":["role"],"description":"Create Role","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Short, human-readable identifier for the role (e.g. `admin`, `editor`).","example":"admin"},"description":{"type":"string","description":"Free-form explanation of what users in this role are expected to do or have access to."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"RoleCreate","additionalProperties":false}}},"description":"RoleCreate"},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Role","content":{"application/json":{"schema":{"type":"object","required":["id","name","description","permissions","account","createdAt"],"properties":{"id":{"type":"string","description":"Role ID"},"name":{"type":"string","description":"Short, human-readable identifier for the role (e.g. `admin`, `editor`, `billing-manager`). Surfaced in the dashboard role pickers.","example":"admin"},"description":{"type":"string","description":"Free-form explanation of what users in this role are expected to do or have access to. Shown alongside the role in the dashboard."},"permissions":{"type":"array","items":{"type":"object","required":["api","value"],"properties":{"api":{"type":"string","description":"Id of the Api that defines the permission.","example":"api_6555fd293acc2f0fac0e3452"},"value":{"type":"string","description":"The permission, as it appears in the Api's `permissions` catalog.","example":"invites:write"}},"additionalProperties":false,"description":"A permission of one Api granted by a role."},"description":"Api permissions granted to the users that hold this role at the account level (team roles grant none). Change them with `POST` / `DELETE /role/:role_id/permissions`; `GET /role/:role_id/permissions` lists them with their Api."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"RoleMetadata","default":{}},"createdAt":{"type":"string","description":"Role creation date"},"updatedAt":{"type":"string","description":"Role updated date"}},"description":"Role"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/role/{role_id}":{"get":{"operationId":"role/get","summary":"Get Role","tags":["role"],"description":"Get Role","parameters":[{"schema":{"type":"string"},"in":"path","name":"role_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Role","content":{"application/json":{"schema":{"type":"object","required":["id","name","description","permissions","account","createdAt"],"properties":{"id":{"type":"string","description":"Role ID"},"name":{"type":"string","description":"Short, human-readable identifier for the role (e.g. `admin`, `editor`, `billing-manager`). Surfaced in the dashboard role pickers.","example":"admin"},"description":{"type":"string","description":"Free-form explanation of what users in this role are expected to do or have access to. Shown alongside the role in the dashboard."},"permissions":{"type":"array","items":{"type":"object","required":["api","value"],"properties":{"api":{"type":"string","description":"Id of the Api that defines the permission.","example":"api_6555fd293acc2f0fac0e3452"},"value":{"type":"string","description":"The permission, as it appears in the Api's `permissions` catalog.","example":"invites:write"}},"additionalProperties":false,"description":"A permission of one Api granted by a role."},"description":"Api permissions granted to the users that hold this role at the account level (team roles grant none). Change them with `POST` / `DELETE /role/:role_id/permissions`; `GET /role/:role_id/permissions` lists them with their Api."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"RoleMetadata","default":{}},"createdAt":{"type":"string","description":"Role creation date"},"updatedAt":{"type":"string","description":"Role updated date"}},"description":"Role"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"role/update","summary":"Update Role","tags":["role"],"description":"Update Role","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"New human-readable identifier for the role."},"description":{"type":"string","description":"New free-form description of what users in this role do."}},"description":"Partial update for a Role. Only the supplied fields are modified.","additionalProperties":false}}},"description":"Partial update for a Role. Only the supplied fields are modified."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"role_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Role","content":{"application/json":{"schema":{"type":"object","required":["id","name","description","permissions","account","createdAt"],"properties":{"id":{"type":"string","description":"Role ID"},"name":{"type":"string","description":"Short, human-readable identifier for the role (e.g. `admin`, `editor`, `billing-manager`). Surfaced in the dashboard role pickers.","example":"admin"},"description":{"type":"string","description":"Free-form explanation of what users in this role are expected to do or have access to. Shown alongside the role in the dashboard."},"permissions":{"type":"array","items":{"type":"object","required":["api","value"],"properties":{"api":{"type":"string","description":"Id of the Api that defines the permission.","example":"api_6555fd293acc2f0fac0e3452"},"value":{"type":"string","description":"The permission, as it appears in the Api's `permissions` catalog.","example":"invites:write"}},"additionalProperties":false,"description":"A permission of one Api granted by a role."},"description":"Api permissions granted to the users that hold this role at the account level (team roles grant none). Change them with `POST` / `DELETE /role/:role_id/permissions`; `GET /role/:role_id/permissions` lists them with their Api."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"RoleMetadata","default":{}},"createdAt":{"type":"string","description":"Role creation date"},"updatedAt":{"type":"string","description":"Role updated date"}},"description":"Role"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"role/delete","summary":"Delete Role","tags":["role"],"description":"Delete Role","parameters":[{"schema":{"type":"string"},"in":"path","name":"role_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Role","content":{"application/json":{"schema":{"type":"object","required":["id","name","description","permissions","account","createdAt"],"properties":{"id":{"type":"string","description":"Role ID"},"name":{"type":"string","description":"Short, human-readable identifier for the role (e.g. `admin`, `editor`, `billing-manager`). Surfaced in the dashboard role pickers.","example":"admin"},"description":{"type":"string","description":"Free-form explanation of what users in this role are expected to do or have access to. Shown alongside the role in the dashboard."},"permissions":{"type":"array","items":{"type":"object","required":["api","value"],"properties":{"api":{"type":"string","description":"Id of the Api that defines the permission.","example":"api_6555fd293acc2f0fac0e3452"},"value":{"type":"string","description":"The permission, as it appears in the Api's `permissions` catalog.","example":"invites:write"}},"additionalProperties":false,"description":"A permission of one Api granted by a role."},"description":"Api permissions granted to the users that hold this role at the account level (team roles grant none). Change them with `POST` / `DELETE /role/:role_id/permissions`; `GET /role/:role_id/permissions` lists them with their Api."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"RoleMetadata","default":{}},"createdAt":{"type":"string","description":"Role creation date"},"updatedAt":{"type":"string","description":"Role updated date"}},"description":"Role"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/role/{role_id}/users":{"get":{"operationId":"role/users","summary":"List Users with Role","tags":["role"],"description":"List Users with Role","parameters":[{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Start from this cursor"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Start from this cursor"},{"schema":{"type":"number"},"in":"query","name":"pageSize","required":false,"description":"Size of the results array"},{"schema":{"type":"string"},"in":"path","name":"role_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"PaginatedResponse","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"type":"string","description":"next cursor","nullable":true},"results":{"type":"array","items":{"$ref":"#/components/schemas/RoleMember"},"description":"List of results"}},"description":"PaginatedResponse","additionalProperties":false}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`role_not_found` — No role with that id in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["role_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"role/assign_users","summary":"Assign Users to Role","tags":["role"],"description":"Assign Users to Role","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["users"],"properties":{"users":{"type":"array","items":{"type":"string"},"description":"List of User ids to grant the role to. Existing role memberships are preserved; passing a user that already holds the role is a no-op.","example":["user_6555fd293acc2f0fac0e3452"]}},"additionalProperties":false,"description":"Payload for granting a role to one or more users in a single call. Posted to `POST /role/:role_id/users`."}}},"description":"Payload for granting a role to one or more users in a single call. Posted to `POST /role/:role_id/users`."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"role_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_role` — The role belongs to another account.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_role","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`role_not_found` — No role with that id in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["role_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"role/remove_users","summary":"Remove Users from Role","tags":["role"],"description":"Revokes an account-level role from users. Their next tokens (refreshes included) no longer carry its permissions; tokens already issued keep them until they expire. Leaves an entry in the tenant's log.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["users"],"properties":{"users":{"type":"array","items":{"type":"string"},"minItems":1,"description":"User ids to revoke the role from. A user that does not hold it is reported in `not_in_role`, not an error.","example":["user_6555fd293acc2f0fac0e3452"]}},"additionalProperties":false,"description":"Payload for revoking a role from one or more users. Sent to `DELETE /role/:role_id/users`."}}},"description":"Payload for revoking a role from one or more users. Sent to `DELETE /role/:role_id/users`."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"role_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["removed_from_role","not_in_role"],"properties":{"removed_from_role":{"type":"array","items":{"type":"string"}},"not_in_role":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}}},"400":{"description":"`invalid_role` — The role belongs to another account.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_role","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}/roles":{"get":{"operationId":"user/roles","summary":"List a User's roles","tags":["user"],"description":"Lists the roles assigned to the User at the account level, by name. Account-level roles are what grant Api permissions to a user (in tokens for an Api with `rbac` enabled). Team roles are separate: they live on the team membership and grant no permissions.","parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["id","name","description","permissions","account","createdAt"],"properties":{"id":{"type":"string","description":"Role ID"},"name":{"type":"string","description":"Short, human-readable identifier for the role (e.g. `admin`, `editor`, `billing-manager`). Surfaced in the dashboard role pickers.","example":"admin"},"description":{"type":"string","description":"Free-form explanation of what users in this role are expected to do or have access to. Shown alongside the role in the dashboard."},"permissions":{"type":"array","items":{"type":"object","required":["api","value"],"properties":{"api":{"type":"string","description":"Id of the Api that defines the permission.","example":"api_6555fd293acc2f0fac0e3452"},"value":{"type":"string","description":"The permission, as it appears in the Api's `permissions` catalog.","example":"invites:write"}},"additionalProperties":false,"description":"A permission of one Api granted by a role."},"description":"Api permissions granted to the users that hold this role at the account level (team roles grant none). Change them with `POST` / `DELETE /role/:role_id/permissions`; `GET /role/:role_id/permissions` lists them with their Api."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"RoleMetadata","default":{}},"createdAt":{"type":"string","description":"Role creation date"},"updatedAt":{"type":"string","description":"Role updated date"}},"description":"Role"}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"user/assign_roles","summary":"Assign roles to a User","tags":["user"],"description":"Assigns account-level roles to the User and returns all of their roles. Roles the User already has are kept. Their next tokens (refreshes included) carry the new permissions. Leaves an entry in the tenant's log. Account-level roles are what grant Api permissions to a user (in tokens for an Api with `rbac` enabled). Team roles are separate: they live on the team membership and grant no permissions.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["roles"],"properties":{"roles":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Role ids of this tenant.","example":["role_6555fd293acc2f0fac0e3452"]},"actor_user_id":{"type":"string","description":"The person making the change, when your backend calls on their behalf with a machine token: it is who the tenant's log names. Must be a user of this tenant."}},"additionalProperties":false,"description":"Account-level roles to assign to or remove from a user."}}},"description":"Account-level roles to assign to or remove from a user."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["id","name","description","permissions","account","createdAt"],"properties":{"id":{"type":"string","description":"Role ID"},"name":{"type":"string","description":"Short, human-readable identifier for the role (e.g. `admin`, `editor`, `billing-manager`). Surfaced in the dashboard role pickers.","example":"admin"},"description":{"type":"string","description":"Free-form explanation of what users in this role are expected to do or have access to. Shown alongside the role in the dashboard."},"permissions":{"type":"array","items":{"type":"object","required":["api","value"],"properties":{"api":{"type":"string","description":"Id of the Api that defines the permission.","example":"api_6555fd293acc2f0fac0e3452"},"value":{"type":"string","description":"The permission, as it appears in the Api's `permissions` catalog.","example":"invites:write"}},"additionalProperties":false,"description":"A permission of one Api granted by a role."},"description":"Api permissions granted to the users that hold this role at the account level (team roles grant none). Change them with `POST` / `DELETE /role/:role_id/permissions`; `GET /role/:role_id/permissions` lists them with their Api."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"RoleMetadata","default":{}},"createdAt":{"type":"string","description":"Role creation date"},"updatedAt":{"type":"string","description":"Role updated date"}},"description":"Role"}}}}},"400":{"description":"`invalid_role` — The role belongs to another account.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_role","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"user/remove_roles","summary":"Remove roles from a User","tags":["user"],"description":"Removes account-level roles from the User and returns the ones left. Roles the User does not have are ignored. Tokens already issued keep the permissions until they expire; refreshes drop them. Leaves an entry in the tenant's log.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["roles"],"properties":{"roles":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Role ids of this tenant.","example":["role_6555fd293acc2f0fac0e3452"]},"actor_user_id":{"type":"string","description":"The person making the change, when your backend calls on their behalf with a machine token: it is who the tenant's log names. Must be a user of this tenant."}},"additionalProperties":false,"description":"Account-level roles to assign to or remove from a user."}}},"description":"Account-level roles to assign to or remove from a user."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["id","name","description","permissions","account","createdAt"],"properties":{"id":{"type":"string","description":"Role ID"},"name":{"type":"string","description":"Short, human-readable identifier for the role (e.g. `admin`, `editor`, `billing-manager`). Surfaced in the dashboard role pickers.","example":"admin"},"description":{"type":"string","description":"Free-form explanation of what users in this role are expected to do or have access to. Shown alongside the role in the dashboard."},"permissions":{"type":"array","items":{"type":"object","required":["api","value"],"properties":{"api":{"type":"string","description":"Id of the Api that defines the permission.","example":"api_6555fd293acc2f0fac0e3452"},"value":{"type":"string","description":"The permission, as it appears in the Api's `permissions` catalog.","example":"invites:write"}},"additionalProperties":false,"description":"A permission of one Api granted by a role."},"description":"Api permissions granted to the users that hold this role at the account level (team roles grant none). Change them with `POST` / `DELETE /role/:role_id/permissions`; `GET /role/:role_id/permissions` lists them with their Api."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"RoleMetadata","default":{}},"createdAt":{"type":"string","description":"Role creation date"},"updatedAt":{"type":"string","description":"Role updated date"}},"description":"Role"}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}/permissions":{"get":{"operationId":"user/permissions","summary":"List a User's permissions","tags":["user"],"description":"Lists the Api permissions the User holds through their account-level roles, with the roles that grant each one. For an Api with `rbac` enabled, these are exactly what the User's tokens for that Api carry in `permissions`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["api","api_identifier","api_name","value","roles"],"properties":{"api":{"type":"string","description":"Id of the Api that defines it."},"api_identifier":{"type":"string","description":"Audience of that Api: the `aud` of the tokens that carry this permission.","example":"https://api.example.com"},"api_name":{"type":"string","description":"Name of that Api."},"value":{"type":"string","description":"The permission, as it appears in the `permissions` claim.","example":"invites:write"},"description":{"type":"string","description":"The permission's description in the Api."},"roles":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"}},"additionalProperties":false},"description":"The roles of the user that grant it."}},"additionalProperties":false,"description":"A permission the user holds through their account-level roles, with the roles that grant it."}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/rolemember":{"get":{"operationId":"rolemember/list","summary":"List Rolemembers","tags":["rolemember"],"description":"List Rolemembers","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"array","items":{"type":"string","enum":["user","role"],"description":"Allowed expand paths on `rolemember`: `user`, `role`."},"maxItems":2},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `?expand=user`)."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/RoleMember"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/rolemember/{rolemember_id}":{"get":{"operationId":"rolemember/get","summary":"Get Rolemember","tags":["rolemember"],"description":"Get Rolemember","parameters":[{"schema":{"type":"array","items":{"type":"string","enum":["user","role"],"description":"Allowed expand paths on `rolemember`: `user`, `role`."},"maxItems":2},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `?expand=user`)."},{"schema":{"type":"string"},"in":"path","name":"rolemember_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"RoleMember","content":{"application/json":{"schema":{"type":"object","required":["id","role","user","account","createdAt"],"properties":{"id":{"type":"string","description":"RoleMember ID"},"role":{"anyOf":[{"$ref":"#/components/schemas/Role"},{"type":"string"},{}],"description":"Role granted to the user. Returned as an id by default; pass `?expand=role` to inline the full Role object."},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}],"description":"User the role is granted to. Returned as an id by default; pass `?expand=user` to inline the full User object."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"RoleMemberMetadata","default":{}},"createdAt":{"type":"string","description":"RoleMember creation date"},"updatedAt":{"type":"string","description":"RoleMember updated date"}},"description":"RoleMember"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"rolemember/delete","summary":"Delete Rolemember","tags":["rolemember"],"description":"Delete Rolemember","parameters":[{"schema":{"type":"string"},"in":"path","name":"rolemember_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"RoleMember","content":{"application/json":{"schema":{"type":"object","required":["id","role","user","account","createdAt"],"properties":{"id":{"type":"string","description":"RoleMember ID"},"role":{"anyOf":[{"$ref":"#/components/schemas/Role"},{"type":"string"},{}],"description":"Role granted to the user. Returned as an id by default; pass `?expand=role` to inline the full Role object."},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}],"description":"User the role is granted to. Returned as an id by default; pass `?expand=user` to inline the full User object."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"RoleMemberMetadata","default":{}},"createdAt":{"type":"string","description":"RoleMember creation date"},"updatedAt":{"type":"string","description":"RoleMember updated date"}},"description":"RoleMember"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/team":{"get":{"operationId":"team/list","summary":"List Teams","tags":["team"],"description":"List Teams","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"query","name":"q","required":false,"description":"Full-text search across: `name`, `slug`."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Team"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"team/create","summary":"Create Team","tags":["team"],"description":"Create Team","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Human-readable name of the team.","example":"Acme Engineering"},"description":{"type":"string","description":"Optional free-form description of the team."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"TeamCreate","additionalProperties":false}}},"description":"TeamCreate"},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Team","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","account","createdAt"],"properties":{"id":{"type":"string","description":"Team ID"},"name":{"type":"string","description":"Human-readable name of the team. Shown in the dashboard and in team-picker UI.","example":"Acme Engineering"},"slug":{"type":"string","description":"URL-safe identifier auto-derived from `name`. Unique within the tenant; used in routes that address a team by slug.","example":"acme-engineering"},"description":{"type":"string","description":"Optional free-form description of the team.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of an image used as the team avatar/logo.","nullable":true},"organization":{"type":"string","description":"Id of the Organization the team belongs to, or `null`. Changed only with `PUT /team/{team_id}/organization`.","nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMetadata","default":{}},"createdAt":{"type":"string","description":"Team creation date"},"updatedAt":{"type":"string","description":"Team updated date"}},"description":"Team"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/team/{team_id}":{"get":{"operationId":"team/get","summary":"Get Team","tags":["team"],"description":"Get Team","parameters":[{"schema":{"type":"string"},"in":"path","name":"team_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Team","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","account","createdAt"],"properties":{"id":{"type":"string","description":"Team ID"},"name":{"type":"string","description":"Human-readable name of the team. Shown in the dashboard and in team-picker UI.","example":"Acme Engineering"},"slug":{"type":"string","description":"URL-safe identifier auto-derived from `name`. Unique within the tenant; used in routes that address a team by slug.","example":"acme-engineering"},"description":{"type":"string","description":"Optional free-form description of the team.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of an image used as the team avatar/logo.","nullable":true},"organization":{"type":"string","description":"Id of the Organization the team belongs to, or `null`. Changed only with `PUT /team/{team_id}/organization`.","nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMetadata","default":{}},"createdAt":{"type":"string","description":"Team creation date"},"updatedAt":{"type":"string","description":"Team updated date"}},"description":"Team"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"team/update","summary":"Update Team","tags":["team"],"description":"Update Team","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"New human-readable name for the team."},"description":{"type":"string","description":"New free-form description for the team."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Arbitrary key/value pairs attached to the team. Replaces the previous metadata entirely."}},"description":"Partial update for a Team. Only the supplied fields are modified. `slug` is auto-derived from `name` and is not user-editable.","additionalProperties":false}}},"description":"Partial update for a Team. Only the supplied fields are modified. `slug` is auto-derived from `name` and is not user-editable."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"team_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Team","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","account","createdAt"],"properties":{"id":{"type":"string","description":"Team ID"},"name":{"type":"string","description":"Human-readable name of the team. Shown in the dashboard and in team-picker UI.","example":"Acme Engineering"},"slug":{"type":"string","description":"URL-safe identifier auto-derived from `name`. Unique within the tenant; used in routes that address a team by slug.","example":"acme-engineering"},"description":{"type":"string","description":"Optional free-form description of the team.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of an image used as the team avatar/logo.","nullable":true},"organization":{"type":"string","description":"Id of the Organization the team belongs to, or `null`. Changed only with `PUT /team/{team_id}/organization`.","nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMetadata","default":{}},"createdAt":{"type":"string","description":"Team creation date"},"updatedAt":{"type":"string","description":"Team updated date"}},"description":"Team"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"team/delete","summary":"Delete Team","tags":["team"],"description":"Delete Team","parameters":[{"schema":{"type":"string"},"in":"path","name":"team_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Team","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","account","createdAt"],"properties":{"id":{"type":"string","description":"Team ID"},"name":{"type":"string","description":"Human-readable name of the team. Shown in the dashboard and in team-picker UI.","example":"Acme Engineering"},"slug":{"type":"string","description":"URL-safe identifier auto-derived from `name`. Unique within the tenant; used in routes that address a team by slug.","example":"acme-engineering"},"description":{"type":"string","description":"Optional free-form description of the team.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of an image used as the team avatar/logo.","nullable":true},"organization":{"type":"string","description":"Id of the Organization the team belongs to, or `null`. Changed only with `PUT /team/{team_id}/organization`.","nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMetadata","default":{}},"createdAt":{"type":"string","description":"Team creation date"},"updatedAt":{"type":"string","description":"Team updated date"}},"description":"Team"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"put":{"operationId":"team/put","summary":"Create or update a Team with a given id","tags":["team"],"description":"Creates the Team with the given id if it does not exist in this tenant (201), or updates its name, description, metadata and — when given — slug (200). Idempotent. Needs both `create:teams` and `update:teams`. `team_id_unavailable` (409) when the id cannot be used in this tenant; `already_exists` when the slug is taken by another team.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"description":"Human-readable name of the team.","example":"Acme Engineering"},"slug":{"type":"string","minLength":1,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","description":"URL-safe identifier, unique within the tenant. Derived from `name` when omitted on creation; when given, it is set as-is (also on update). Pass it when names can repeat.","example":"acme-engineering"},"description":{"type":"string","description":"Optional free-form description."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Arbitrary key/value pairs attached to the team. Replaces the previous metadata entirely."}},"additionalProperties":false,"description":"Payload for creating or updating a Team under a caller-chosen id."}}},"description":"Payload for creating or updating a Team under a caller-chosen id."},"parameters":[{"schema":{"type":"string","pattern":"^team_[0-9a-f]{24}$"},"in":"path","name":"team_id","required":true,"description":"Id of the team to create or update: `team_` followed by 24 lowercase hex characters, chosen by the caller."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Team","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","account","createdAt"],"properties":{"id":{"type":"string","description":"Team ID"},"name":{"type":"string","description":"Human-readable name of the team. Shown in the dashboard and in team-picker UI.","example":"Acme Engineering"},"slug":{"type":"string","description":"URL-safe identifier auto-derived from `name`. Unique within the tenant; used in routes that address a team by slug.","example":"acme-engineering"},"description":{"type":"string","description":"Optional free-form description of the team.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of an image used as the team avatar/logo.","nullable":true},"organization":{"type":"string","description":"Id of the Organization the team belongs to, or `null`. Changed only with `PUT /team/{team_id}/organization`.","nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMetadata","default":{}},"createdAt":{"type":"string","description":"Team creation date"},"updatedAt":{"type":"string","description":"Team updated date"}},"description":"Team"}}}},"201":{"description":"Team","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","account","createdAt"],"properties":{"id":{"type":"string","description":"Team ID"},"name":{"type":"string","description":"Human-readable name of the team. Shown in the dashboard and in team-picker UI.","example":"Acme Engineering"},"slug":{"type":"string","description":"URL-safe identifier auto-derived from `name`. Unique within the tenant; used in routes that address a team by slug.","example":"acme-engineering"},"description":{"type":"string","description":"Optional free-form description of the team.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of an image used as the team avatar/logo.","nullable":true},"organization":{"type":"string","description":"Id of the Organization the team belongs to, or `null`. Changed only with `PUT /team/{team_id}/organization`.","nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMetadata","default":{}},"createdAt":{"type":"string","description":"Team creation date"},"updatedAt":{"type":"string","description":"Team updated date"}},"description":"Team"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"409":{"description":"`team_id_unavailable` — The team id cannot be used in this tenant. Pick another id or let the API generate one.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["team_id_unavailable"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/team/{team_id}/organization":{"put":{"operationId":"team/setOrganization","summary":"Put a Team in an Organization, or take it out","tags":["team"],"description":"Sets the Organization the Team belongs to (`null` = none). A team is in at most one organization; setting another one moves it. Belonging to an organization grants nobody access to the team. Emits `team.updated`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["organization"],"properties":{"organization":{"type":"string","description":"Id of an Organization of this tenant, or `null` to take the team out of its organization.","nullable":true}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"team_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Team","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","account","createdAt"],"properties":{"id":{"type":"string","description":"Team ID"},"name":{"type":"string","description":"Human-readable name of the team. Shown in the dashboard and in team-picker UI.","example":"Acme Engineering"},"slug":{"type":"string","description":"URL-safe identifier auto-derived from `name`. Unique within the tenant; used in routes that address a team by slug.","example":"acme-engineering"},"description":{"type":"string","description":"Optional free-form description of the team.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of an image used as the team avatar/logo.","nullable":true},"organization":{"type":"string","description":"Id of the Organization the team belongs to, or `null`. Changed only with `PUT /team/{team_id}/organization`.","nullable":true},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMetadata","default":{}},"createdAt":{"type":"string","description":"Team creation date"},"updatedAt":{"type":"string","description":"Team updated date"}},"description":"Team"}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`organization_not_found` — No organization with that id in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","organization_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/organization":{"get":{"operationId":"organization/list","summary":"List Organizations","tags":["organization"],"description":"List Organizations","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"query","name":"q","required":false,"description":"Full-text search across: `name`, `slug`."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Organization"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"organization/create","summary":"Create Organization","tags":["organization"],"description":"Create Organization","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"description":"Human-readable name of the organization.","example":"Acme Inc."},"description":{"type":"string","description":"Optional free-form description."},"logo_url":{"type":"string","format":"uri","description":"Optional URL of the organization logo."},"admins":{"type":"array","items":{"type":"string","pattern":"^user_[0-9a-f]{24}$"},"uniqueItems":true,"description":"Initial admins (user ids of this tenant). Can be empty and filled later with `POST /organization/{organization_id}/admin`."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"OrganizationCreate","additionalProperties":false}}},"description":"OrganizationCreate"},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Organization","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","domains","require_sso","auto_join","admins","account","createdAt"],"properties":{"id":{"type":"string","description":"Organization ID"},"name":{"type":"string","description":"Human-readable name of the organization.","example":"Acme Inc."},"slug":{"type":"string","description":"URL-safe identifier derived from `name`. Unique within the tenant.","example":"acme-inc"},"description":{"type":"string","description":"Optional free-form description.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of the organization logo.","nullable":true},"domains":{"type":"array","items":{"type":"object","required":["domain","verification_token","verified_at"],"properties":{"domain":{"type":"string","example":"acme.com"},"verification_token":{"type":"string","description":"Put `faable-verification=<verification_token>` in a TXT record at `_faable-challenge.<domain>`, then call `POST /organization/{organization_id}/domain/{domain}/verify`."},"verified_at":{"type":"string","format":"date-time","description":"When the TXT record was found. `null` = not verified.","nullable":true}}},"description":"Email domains the organization claims. Only a verified one counts: it is how the organization's people are recognized."},"require_sso":{"type":"boolean","description":"People of the verified domains reach the organization's teams only through its identity provider. Applied by the resource server with the `https://faable.com/org` and `https://faable.com/org_sso` access-token claims."},"auto_join":{"type":"array","items":{"type":"object","required":["team","roles"],"properties":{"team":{"type":"string","description":"A team of this organization."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids the member gets in that team."}}},"description":"Whoever signs in with a verified email of one of the verified domains joins these teams. Empty = nobody joins by domain."},"admins":{"type":"array","items":{"type":"string"},"description":"Ids of the users who manage the organization: its teams and its identity settings. Being an admin does not grant access to any team; team access is team membership."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"OrganizationMetadata","default":{}},"createdAt":{"type":"string","description":"Organization creation date"},"updatedAt":{"type":"string","description":"Organization updated date"}},"description":"Organization"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/organization/{organization_id}":{"get":{"operationId":"organization/get","summary":"Get Organization","tags":["organization"],"description":"Get Organization","parameters":[{"schema":{"type":"string"},"in":"path","name":"organization_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Organization","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","domains","require_sso","auto_join","admins","account","createdAt"],"properties":{"id":{"type":"string","description":"Organization ID"},"name":{"type":"string","description":"Human-readable name of the organization.","example":"Acme Inc."},"slug":{"type":"string","description":"URL-safe identifier derived from `name`. Unique within the tenant.","example":"acme-inc"},"description":{"type":"string","description":"Optional free-form description.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of the organization logo.","nullable":true},"domains":{"type":"array","items":{"type":"object","required":["domain","verification_token","verified_at"],"properties":{"domain":{"type":"string","example":"acme.com"},"verification_token":{"type":"string","description":"Put `faable-verification=<verification_token>` in a TXT record at `_faable-challenge.<domain>`, then call `POST /organization/{organization_id}/domain/{domain}/verify`."},"verified_at":{"type":"string","format":"date-time","description":"When the TXT record was found. `null` = not verified.","nullable":true}}},"description":"Email domains the organization claims. Only a verified one counts: it is how the organization's people are recognized."},"require_sso":{"type":"boolean","description":"People of the verified domains reach the organization's teams only through its identity provider. Applied by the resource server with the `https://faable.com/org` and `https://faable.com/org_sso` access-token claims."},"auto_join":{"type":"array","items":{"type":"object","required":["team","roles"],"properties":{"team":{"type":"string","description":"A team of this organization."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids the member gets in that team."}}},"description":"Whoever signs in with a verified email of one of the verified domains joins these teams. Empty = nobody joins by domain."},"admins":{"type":"array","items":{"type":"string"},"description":"Ids of the users who manage the organization: its teams and its identity settings. Being an admin does not grant access to any team; team access is team membership."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"OrganizationMetadata","default":{}},"createdAt":{"type":"string","description":"Organization creation date"},"updatedAt":{"type":"string","description":"Organization updated date"}},"description":"Organization"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"organization/update","summary":"Update Organization","tags":["organization"],"description":"Update Organization","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"description":"New human-readable name."},"description":{"type":"string","description":"New free-form description."},"logo_url":{"type":"string","format":"uri","description":"New URL of the organization logo."},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Arbitrary key/value pairs. Replaces the previous metadata entirely."}},"description":"Partial update for an Organization. `slug` is derived from `name` on creation and is not editable; admins change through `/organization/{organization_id}/admin`.","additionalProperties":false}}},"description":"Partial update for an Organization. `slug` is derived from `name` on creation and is not editable; admins change through `/organization/{organization_id}/admin`."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"organization_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Organization","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","domains","require_sso","auto_join","admins","account","createdAt"],"properties":{"id":{"type":"string","description":"Organization ID"},"name":{"type":"string","description":"Human-readable name of the organization.","example":"Acme Inc."},"slug":{"type":"string","description":"URL-safe identifier derived from `name`. Unique within the tenant.","example":"acme-inc"},"description":{"type":"string","description":"Optional free-form description.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of the organization logo.","nullable":true},"domains":{"type":"array","items":{"type":"object","required":["domain","verification_token","verified_at"],"properties":{"domain":{"type":"string","example":"acme.com"},"verification_token":{"type":"string","description":"Put `faable-verification=<verification_token>` in a TXT record at `_faable-challenge.<domain>`, then call `POST /organization/{organization_id}/domain/{domain}/verify`."},"verified_at":{"type":"string","format":"date-time","description":"When the TXT record was found. `null` = not verified.","nullable":true}}},"description":"Email domains the organization claims. Only a verified one counts: it is how the organization's people are recognized."},"require_sso":{"type":"boolean","description":"People of the verified domains reach the organization's teams only through its identity provider. Applied by the resource server with the `https://faable.com/org` and `https://faable.com/org_sso` access-token claims."},"auto_join":{"type":"array","items":{"type":"object","required":["team","roles"],"properties":{"team":{"type":"string","description":"A team of this organization."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids the member gets in that team."}}},"description":"Whoever signs in with a verified email of one of the verified domains joins these teams. Empty = nobody joins by domain."},"admins":{"type":"array","items":{"type":"string"},"description":"Ids of the users who manage the organization: its teams and its identity settings. Being an admin does not grant access to any team; team access is team membership."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"OrganizationMetadata","default":{}},"createdAt":{"type":"string","description":"Organization creation date"},"updatedAt":{"type":"string","description":"Organization updated date"}},"description":"Organization"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"organization/delete","summary":"Delete Organization","tags":["organization"],"description":"Delete Organization","parameters":[{"schema":{"type":"string"},"in":"path","name":"organization_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Organization","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","domains","require_sso","auto_join","admins","account","createdAt"],"properties":{"id":{"type":"string","description":"Organization ID"},"name":{"type":"string","description":"Human-readable name of the organization.","example":"Acme Inc."},"slug":{"type":"string","description":"URL-safe identifier derived from `name`. Unique within the tenant.","example":"acme-inc"},"description":{"type":"string","description":"Optional free-form description.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of the organization logo.","nullable":true},"domains":{"type":"array","items":{"type":"object","required":["domain","verification_token","verified_at"],"properties":{"domain":{"type":"string","example":"acme.com"},"verification_token":{"type":"string","description":"Put `faable-verification=<verification_token>` in a TXT record at `_faable-challenge.<domain>`, then call `POST /organization/{organization_id}/domain/{domain}/verify`."},"verified_at":{"type":"string","format":"date-time","description":"When the TXT record was found. `null` = not verified.","nullable":true}}},"description":"Email domains the organization claims. Only a verified one counts: it is how the organization's people are recognized."},"require_sso":{"type":"boolean","description":"People of the verified domains reach the organization's teams only through its identity provider. Applied by the resource server with the `https://faable.com/org` and `https://faable.com/org_sso` access-token claims."},"auto_join":{"type":"array","items":{"type":"object","required":["team","roles"],"properties":{"team":{"type":"string","description":"A team of this organization."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids the member gets in that team."}}},"description":"Whoever signs in with a verified email of one of the verified domains joins these teams. Empty = nobody joins by domain."},"admins":{"type":"array","items":{"type":"string"},"description":"Ids of the users who manage the organization: its teams and its identity settings. Being an admin does not grant access to any team; team access is team membership."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"OrganizationMetadata","default":{}},"createdAt":{"type":"string","description":"Organization creation date"},"updatedAt":{"type":"string","description":"Organization updated date"}},"description":"Organization"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/organization/{organization_id}/admin":{"post":{"operationId":"organization/addAdmin","summary":"Add an admin to an Organization","tags":["organization"],"description":"Makes a user of this tenant an admin of the Organization. Idempotent. Being an admin does not grant access to any team of the organization. Emits `organization.updated`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_id"],"properties":{"user_id":{"type":"string","description":"Id of a user of this tenant.","example":"user_6555fd29a1b2c3d4e5f60718"}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"organization_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Organization","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","domains","require_sso","auto_join","admins","account","createdAt"],"properties":{"id":{"type":"string","description":"Organization ID"},"name":{"type":"string","description":"Human-readable name of the organization.","example":"Acme Inc."},"slug":{"type":"string","description":"URL-safe identifier derived from `name`. Unique within the tenant.","example":"acme-inc"},"description":{"type":"string","description":"Optional free-form description.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of the organization logo.","nullable":true},"domains":{"type":"array","items":{"type":"object","required":["domain","verification_token","verified_at"],"properties":{"domain":{"type":"string","example":"acme.com"},"verification_token":{"type":"string","description":"Put `faable-verification=<verification_token>` in a TXT record at `_faable-challenge.<domain>`, then call `POST /organization/{organization_id}/domain/{domain}/verify`."},"verified_at":{"type":"string","format":"date-time","description":"When the TXT record was found. `null` = not verified.","nullable":true}}},"description":"Email domains the organization claims. Only a verified one counts: it is how the organization's people are recognized."},"require_sso":{"type":"boolean","description":"People of the verified domains reach the organization's teams only through its identity provider. Applied by the resource server with the `https://faable.com/org` and `https://faable.com/org_sso` access-token claims."},"auto_join":{"type":"array","items":{"type":"object","required":["team","roles"],"properties":{"team":{"type":"string","description":"A team of this organization."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids the member gets in that team."}}},"description":"Whoever signs in with a verified email of one of the verified domains joins these teams. Empty = nobody joins by domain."},"admins":{"type":"array","items":{"type":"string"},"description":"Ids of the users who manage the organization: its teams and its identity settings. Being an admin does not grant access to any team; team access is team membership."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"OrganizationMetadata","default":{}},"createdAt":{"type":"string","description":"Organization creation date"},"updatedAt":{"type":"string","description":"Organization updated date"}},"description":"Organization"}}}},"400":{"description":"`invalid_user` — The user does not exist in this account.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_user","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`organization_not_found` — No organization with that id in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["organization_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/organization/{organization_id}/admin/{user_id}":{"delete":{"operationId":"organization/removeAdmin","summary":"Remove an admin from an Organization","tags":["organization"],"description":"Removes the user from the admins of the Organization (the user is not deleted). An organization keeps at least one admin: removing the last one is `last_organization_admin`. Emits `organization.updated`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"organization_id","required":true},{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Organization","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","domains","require_sso","auto_join","admins","account","createdAt"],"properties":{"id":{"type":"string","description":"Organization ID"},"name":{"type":"string","description":"Human-readable name of the organization.","example":"Acme Inc."},"slug":{"type":"string","description":"URL-safe identifier derived from `name`. Unique within the tenant.","example":"acme-inc"},"description":{"type":"string","description":"Optional free-form description.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of the organization logo.","nullable":true},"domains":{"type":"array","items":{"type":"object","required":["domain","verification_token","verified_at"],"properties":{"domain":{"type":"string","example":"acme.com"},"verification_token":{"type":"string","description":"Put `faable-verification=<verification_token>` in a TXT record at `_faable-challenge.<domain>`, then call `POST /organization/{organization_id}/domain/{domain}/verify`."},"verified_at":{"type":"string","format":"date-time","description":"When the TXT record was found. `null` = not verified.","nullable":true}}},"description":"Email domains the organization claims. Only a verified one counts: it is how the organization's people are recognized."},"require_sso":{"type":"boolean","description":"People of the verified domains reach the organization's teams only through its identity provider. Applied by the resource server with the `https://faable.com/org` and `https://faable.com/org_sso` access-token claims."},"auto_join":{"type":"array","items":{"type":"object","required":["team","roles"],"properties":{"team":{"type":"string","description":"A team of this organization."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids the member gets in that team."}}},"description":"Whoever signs in with a verified email of one of the verified domains joins these teams. Empty = nobody joins by domain."},"admins":{"type":"array","items":{"type":"string"},"description":"Ids of the users who manage the organization: its teams and its identity settings. Being an admin does not grant access to any team; team access is team membership."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"OrganizationMetadata","default":{}},"createdAt":{"type":"string","description":"Organization creation date"},"updatedAt":{"type":"string","description":"Organization updated date"}},"description":"Organization"}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`organization_not_found` — No organization with that id in this account.\n\n`not_an_organization_admin` — The user is not an admin of the organization.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["organization_not_found","not_an_organization_admin","account_not_found"]}}}]}}}},"409":{"description":"`last_organization_admin` — An organization keeps at least one admin. Add another admin before removing this one.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["last_organization_admin"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/organization/{organization_id}/domain":{"post":{"operationId":"organization/addDomain","summary":"Claim an email domain for an Organization","tags":["organization"],"description":"Adds the domain unverified, with the `verification_token` its TXT record must hold. Idempotent. Nothing changes for anyone until the domain is verified.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","description":"The email domain, as in `alice@<domain>`.","example":"acme.com"}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"organization_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Organization","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","domains","require_sso","auto_join","admins","account","createdAt"],"properties":{"id":{"type":"string","description":"Organization ID"},"name":{"type":"string","description":"Human-readable name of the organization.","example":"Acme Inc."},"slug":{"type":"string","description":"URL-safe identifier derived from `name`. Unique within the tenant.","example":"acme-inc"},"description":{"type":"string","description":"Optional free-form description.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of the organization logo.","nullable":true},"domains":{"type":"array","items":{"type":"object","required":["domain","verification_token","verified_at"],"properties":{"domain":{"type":"string","example":"acme.com"},"verification_token":{"type":"string","description":"Put `faable-verification=<verification_token>` in a TXT record at `_faable-challenge.<domain>`, then call `POST /organization/{organization_id}/domain/{domain}/verify`."},"verified_at":{"type":"string","format":"date-time","description":"When the TXT record was found. `null` = not verified.","nullable":true}}},"description":"Email domains the organization claims. Only a verified one counts: it is how the organization's people are recognized."},"require_sso":{"type":"boolean","description":"People of the verified domains reach the organization's teams only through its identity provider. Applied by the resource server with the `https://faable.com/org` and `https://faable.com/org_sso` access-token claims."},"auto_join":{"type":"array","items":{"type":"object","required":["team","roles"],"properties":{"team":{"type":"string","description":"A team of this organization."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids the member gets in that team."}}},"description":"Whoever signs in with a verified email of one of the verified domains joins these teams. Empty = nobody joins by domain."},"admins":{"type":"array","items":{"type":"string"},"description":"Ids of the users who manage the organization: its teams and its identity settings. Being an admin does not grant access to any team; team access is team membership."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"OrganizationMetadata","default":{}},"createdAt":{"type":"string","description":"Organization creation date"},"updatedAt":{"type":"string","description":"Organization updated date"}},"description":"Organization"}}}},"400":{"description":"`invalid_email_domain` — Not an email domain this organization can claim: it must be a plain domain name (`acme.com`), lowercase, without scheme, path or wildcard.\n\n`public_email_domain` — A public email provider (gmail.com, outlook.com…) cannot belong to an organization: anyone can have an address there.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_email_domain","public_email_domain","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`organization_not_found` — No organization with that id in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["organization_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/organization/{organization_id}/domain/{domain}/verify":{"post":{"operationId":"organization/verifyDomain","summary":"Verify an Organization's email domain","tags":["organization"],"description":"Looks up the TXT record `_faable-challenge.<domain>` and, when it holds `faable-verification=<verification_token>`, marks the domain verified. Pro plan (`plan_required`; `plan_unavailable` when the plan cannot be checked). A domain is verified by one organization of the tenant at most (`organization_domain_taken`).","parameters":[{"schema":{"type":"string"},"in":"path","name":"organization_id","required":true},{"schema":{"type":"string"},"in":"path","name":"domain","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["organization","record"],"properties":{"organization":{"type":"object","required":["id","name","slug","description","logo_url","domains","require_sso","auto_join","admins","account","createdAt"],"properties":{"id":{"type":"string","description":"Organization ID"},"name":{"type":"string","description":"Human-readable name of the organization.","example":"Acme Inc."},"slug":{"type":"string","description":"URL-safe identifier derived from `name`. Unique within the tenant.","example":"acme-inc"},"description":{"type":"string","description":"Optional free-form description.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of the organization logo.","nullable":true},"domains":{"type":"array","items":{"type":"object","required":["domain","verification_token","verified_at"],"properties":{"domain":{"type":"string","example":"acme.com"},"verification_token":{"type":"string","description":"Put `faable-verification=<verification_token>` in a TXT record at `_faable-challenge.<domain>`, then call `POST /organization/{organization_id}/domain/{domain}/verify`."},"verified_at":{"type":"string","format":"date-time","description":"When the TXT record was found. `null` = not verified.","nullable":true}}},"description":"Email domains the organization claims. Only a verified one counts: it is how the organization's people are recognized."},"require_sso":{"type":"boolean","description":"People of the verified domains reach the organization's teams only through its identity provider. Applied by the resource server with the `https://faable.com/org` and `https://faable.com/org_sso` access-token claims."},"auto_join":{"type":"array","items":{"type":"object","required":["team","roles"],"properties":{"team":{"type":"string","description":"A team of this organization."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids the member gets in that team."}}},"description":"Whoever signs in with a verified email of one of the verified domains joins these teams. Empty = nobody joins by domain."},"admins":{"type":"array","items":{"type":"string"},"description":"Ids of the users who manage the organization: its teams and its identity settings. Being an admin does not grant access to any team; team access is team membership."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"OrganizationMetadata","default":{}},"createdAt":{"type":"string","description":"Organization creation date"},"updatedAt":{"type":"string","description":"Organization updated date"}},"description":"Organization"},"record":{"anyOf":[{"type":"object","required":["type","host","expected","observed","ok"],"properties":{"type":{"type":"string","enum":["TXT"]},"host":{"type":"string"},"expected":{"type":"string"},"observed":{"type":"array","items":{"type":"string"}},"ok":{"type":"boolean"}}},{"type":"null"}],"description":"`null` when the domain was already verified."}}}}}},"400":{"description":"`organization_domain_unverified` — The TXT record was not found or does not hold the expected value yet. `details.record` says what was expected and what resolves now; DNS can take a few minutes to propagate.\n\n`invalid_email_domain` — Not an email domain this organization can claim: it must be a plain domain name (`acme.com`), lowercase, without scheme, path or wildcard.\n\n`public_email_domain` — A public email provider (gmail.com, outlook.com…) cannot belong to an organization: anyone can have an address there.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["organization_domain_unverified","invalid_email_domain","public_email_domain","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"402":{"description":"`plan_required` — The setting requires a higher plan. `message` says which.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["plan_required"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`organization_not_found` — No organization with that id in this account.\n\n`organization_domain_not_found` — The organization has no such domain.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["organization_not_found","organization_domain_not_found","account_not_found"]}}}]}}}},"409":{"description":"`organization_domain_taken` — Another organization of this tenant already verified this domain. A domain belongs to one organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["organization_domain_taken"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}},"503":{"description":"`plan_unavailable` — The plan could not be checked right now, and this setting is only for paid plans. Try again in a minute.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["plan_unavailable"]}}}]}}}}}}},"/organization/{organization_id}/domain/{domain}":{"delete":{"operationId":"organization/removeDomain","summary":"Remove an email domain from an Organization","tags":["organization"],"description":"Removes the domain, verified or not. Logins of people already in the organization are unaffected.","parameters":[{"schema":{"type":"string"},"in":"path","name":"organization_id","required":true},{"schema":{"type":"string"},"in":"path","name":"domain","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Organization","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","domains","require_sso","auto_join","admins","account","createdAt"],"properties":{"id":{"type":"string","description":"Organization ID"},"name":{"type":"string","description":"Human-readable name of the organization.","example":"Acme Inc."},"slug":{"type":"string","description":"URL-safe identifier derived from `name`. Unique within the tenant.","example":"acme-inc"},"description":{"type":"string","description":"Optional free-form description.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of the organization logo.","nullable":true},"domains":{"type":"array","items":{"type":"object","required":["domain","verification_token","verified_at"],"properties":{"domain":{"type":"string","example":"acme.com"},"verification_token":{"type":"string","description":"Put `faable-verification=<verification_token>` in a TXT record at `_faable-challenge.<domain>`, then call `POST /organization/{organization_id}/domain/{domain}/verify`."},"verified_at":{"type":"string","format":"date-time","description":"When the TXT record was found. `null` = not verified.","nullable":true}}},"description":"Email domains the organization claims. Only a verified one counts: it is how the organization's people are recognized."},"require_sso":{"type":"boolean","description":"People of the verified domains reach the organization's teams only through its identity provider. Applied by the resource server with the `https://faable.com/org` and `https://faable.com/org_sso` access-token claims."},"auto_join":{"type":"array","items":{"type":"object","required":["team","roles"],"properties":{"team":{"type":"string","description":"A team of this organization."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids the member gets in that team."}}},"description":"Whoever signs in with a verified email of one of the verified domains joins these teams. Empty = nobody joins by domain."},"admins":{"type":"array","items":{"type":"string"},"description":"Ids of the users who manage the organization: its teams and its identity settings. Being an admin does not grant access to any team; team access is team membership."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"OrganizationMetadata","default":{}},"createdAt":{"type":"string","description":"Organization creation date"},"updatedAt":{"type":"string","description":"Organization updated date"}},"description":"Organization"}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`organization_not_found` — No organization with that id in this account.\n\n`organization_domain_not_found` — The organization has no such domain.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["organization_not_found","organization_domain_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/organization/{organization_id}/auto-join":{"put":{"operationId":"organization/setAutoJoin","summary":"Set an Organization's auto-join rules","tags":["organization"],"description":"Replaces the auto-join rules: whoever signs in with a verified email of one of the verified domains joins these teams (of this organization) with these roles. Nobody already a member is changed. Pro plan to turn it on; an empty list turns it off and is always allowed.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["auto_join"],"properties":{"auto_join":{"type":"array","items":{"type":"object","required":["team","roles"],"properties":{"team":{"type":"string","description":"A team of this organization."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids of this tenant."}},"additionalProperties":false},"maxItems":50}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"organization_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Organization","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","domains","require_sso","auto_join","admins","account","createdAt"],"properties":{"id":{"type":"string","description":"Organization ID"},"name":{"type":"string","description":"Human-readable name of the organization.","example":"Acme Inc."},"slug":{"type":"string","description":"URL-safe identifier derived from `name`. Unique within the tenant.","example":"acme-inc"},"description":{"type":"string","description":"Optional free-form description.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of the organization logo.","nullable":true},"domains":{"type":"array","items":{"type":"object","required":["domain","verification_token","verified_at"],"properties":{"domain":{"type":"string","example":"acme.com"},"verification_token":{"type":"string","description":"Put `faable-verification=<verification_token>` in a TXT record at `_faable-challenge.<domain>`, then call `POST /organization/{organization_id}/domain/{domain}/verify`."},"verified_at":{"type":"string","format":"date-time","description":"When the TXT record was found. `null` = not verified.","nullable":true}}},"description":"Email domains the organization claims. Only a verified one counts: it is how the organization's people are recognized."},"require_sso":{"type":"boolean","description":"People of the verified domains reach the organization's teams only through its identity provider. Applied by the resource server with the `https://faable.com/org` and `https://faable.com/org_sso` access-token claims."},"auto_join":{"type":"array","items":{"type":"object","required":["team","roles"],"properties":{"team":{"type":"string","description":"A team of this organization."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids the member gets in that team."}}},"description":"Whoever signs in with a verified email of one of the verified domains joins these teams. Empty = nobody joins by domain."},"admins":{"type":"array","items":{"type":"string"},"description":"Ids of the users who manage the organization: its teams and its identity settings. Being an admin does not grant access to any team; team access is team membership."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"OrganizationMetadata","default":{}},"createdAt":{"type":"string","description":"Organization creation date"},"updatedAt":{"type":"string","description":"Organization updated date"}},"description":"Organization"}}}},"400":{"description":"`team_not_in_organization` — Auto-join can only put people in teams of the same organization. `details.teams` lists the others.\n\n`invalid_role` — The role belongs to another account.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["team_not_in_organization","invalid_role","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"402":{"description":"`plan_required` — The setting requires a higher plan. `message` says which.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["plan_required"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`organization_not_found` — No organization with that id in this account.\n\n`role_not_found` — No role with that id in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["organization_not_found","role_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}},"503":{"description":"`plan_unavailable` — The plan could not be checked right now, and this setting is only for paid plans. Try again in a minute.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["plan_unavailable"]}}}]}}}}}}},"/organization/{organization_id}/require-sso":{"put":{"operationId":"organization/setRequireSso","summary":"Require SSO for an Organization","tags":["organization"],"description":"On: people of the verified domains reach the organization's teams only through its identity provider. Access tokens carry `https://faable.com/org` (the organization of the user's verified email domain) and `https://faable.com/org_sso` (whether this session came through its identity provider); the resource server applies it. Needs a bound `oidc` connection and the Pro plan; turning it off is always allowed.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["require_sso"],"properties":{"require_sso":{"type":"boolean"}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"organization_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Organization","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","description","logo_url","domains","require_sso","auto_join","admins","account","createdAt"],"properties":{"id":{"type":"string","description":"Organization ID"},"name":{"type":"string","description":"Human-readable name of the organization.","example":"Acme Inc."},"slug":{"type":"string","description":"URL-safe identifier derived from `name`. Unique within the tenant.","example":"acme-inc"},"description":{"type":"string","description":"Optional free-form description.","nullable":true},"logo_url":{"type":"string","description":"Optional URL of the organization logo.","nullable":true},"domains":{"type":"array","items":{"type":"object","required":["domain","verification_token","verified_at"],"properties":{"domain":{"type":"string","example":"acme.com"},"verification_token":{"type":"string","description":"Put `faable-verification=<verification_token>` in a TXT record at `_faable-challenge.<domain>`, then call `POST /organization/{organization_id}/domain/{domain}/verify`."},"verified_at":{"type":"string","format":"date-time","description":"When the TXT record was found. `null` = not verified.","nullable":true}}},"description":"Email domains the organization claims. Only a verified one counts: it is how the organization's people are recognized."},"require_sso":{"type":"boolean","description":"People of the verified domains reach the organization's teams only through its identity provider. Applied by the resource server with the `https://faable.com/org` and `https://faable.com/org_sso` access-token claims."},"auto_join":{"type":"array","items":{"type":"object","required":["team","roles"],"properties":{"team":{"type":"string","description":"A team of this organization."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids the member gets in that team."}}},"description":"Whoever signs in with a verified email of one of the verified domains joins these teams. Empty = nobody joins by domain."},"admins":{"type":"array","items":{"type":"string"},"description":"Ids of the users who manage the organization: its teams and its identity settings. Being an admin does not grant access to any team; team access is team membership."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"OrganizationMetadata","default":{}},"createdAt":{"type":"string","description":"Organization creation date"},"updatedAt":{"type":"string","description":"Organization updated date"}},"description":"Organization"}}}},"400":{"description":"`organization_has_no_idp` — Requiring SSO needs an identity provider: bind an `oidc` connection to the organization first.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["organization_has_no_idp","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"402":{"description":"`plan_required` — The setting requires a higher plan. `message` says which.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["plan_required"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`organization_not_found` — No organization with that id in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["organization_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}},"503":{"description":"`plan_unavailable` — The plan could not be checked right now, and this setting is only for paid plans. Try again in a minute.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["plan_unavailable"]}}}]}}}}}}},"/teammember":{"get":{"operationId":"teammember/list","summary":"List Teammembers","tags":["teammember"],"description":"List Teammembers","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"string"},"in":"query","name":"user","required":false,"description":"Exact match on `user`. Equivalent to `?query=user:<value>`."},{"schema":{"type":"string"},"in":"query","name":"team","required":false,"description":"Exact match on `team`. Equivalent to `?query=team:<value>`."},{"schema":{"type":"string"},"in":"query","name":"role","required":false,"description":"Exact match on `role`. Equivalent to `?query=role:<value>`."},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"array","items":{"type":"string","enum":["user","team","roles"],"description":"Allowed expand paths on `teammember`: `user`, `team`, `roles`."},"maxItems":3},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `?expand=user`)."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/teammember/{teammember_id}":{"get":{"operationId":"teammember/get","summary":"Get Teammember","tags":["teammember"],"description":"Get Teammember","parameters":[{"schema":{"type":"array","items":{"type":"string","enum":["user","team","roles"],"description":"Allowed expand paths on `teammember`: `user`, `team`, `roles`."},"maxItems":3},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `?expand=user`)."},{"schema":{"type":"string"},"in":"path","name":"teammember_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"TeamMember","content":{"application/json":{"schema":{"type":"object","required":["id","user","team","roles","account","createdAt"],"properties":{"id":{"type":"string","description":"TeamMember ID"},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}],"description":"User that belongs to the team. Returned as an id by default; pass `?expand=user` to inline the full User object."},"team":{"anyOf":[{"$ref":"#/components/schemas/Team"},{"type":"string"},{}],"description":"Team the user belongs to. Returned as an id by default; pass `?expand=team` to inline the full Team object."},"roles":{"type":"array","items":{"anyOf":[{"$ref":"#/components/schemas/Role"},{"type":"string"},{}]},"description":"Roles granted to the user *within this team*. Distinct from account-wide RoleMember grants — these only apply in the scope of the team. Each entry is an id by default; pass `?expand=roles` to inline."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMemberMetadata","default":{}},"createdAt":{"type":"string","description":"TeamMember creation date"},"updatedAt":{"type":"string","description":"TeamMember updated date"}},"description":"TeamMember"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"teammember/delete","summary":"Delete Teammember","tags":["teammember"],"description":"Delete Teammember","parameters":[{"schema":{"type":"string"},"in":"path","name":"teammember_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"TeamMember","content":{"application/json":{"schema":{"type":"object","required":["id","user","team","roles","account","createdAt"],"properties":{"id":{"type":"string","description":"TeamMember ID"},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}],"description":"User that belongs to the team. Returned as an id by default; pass `?expand=user` to inline the full User object."},"team":{"anyOf":[{"$ref":"#/components/schemas/Team"},{"type":"string"},{}],"description":"Team the user belongs to. Returned as an id by default; pass `?expand=team` to inline the full Team object."},"roles":{"type":"array","items":{"anyOf":[{"$ref":"#/components/schemas/Role"},{"type":"string"},{}]},"description":"Roles granted to the user *within this team*. Distinct from account-wide RoleMember grants — these only apply in the scope of the team. Each entry is an id by default; pass `?expand=roles` to inline."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMemberMetadata","default":{}},"createdAt":{"type":"string","description":"TeamMember creation date"},"updatedAt":{"type":"string","description":"TeamMember updated date"}},"description":"TeamMember"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/team/{team_id}/member":{"get":{"operationId":"team/listMembers","summary":"List Team Members","tags":["team"],"description":"List all Users that belong to the given Team in the current Account. Supports `?expand=user,team,roles` to inline referenced rows.","parameters":[{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Start from this cursor"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Start from this cursor"},{"schema":{"type":"number"},"in":"query","name":"pageSize","required":false,"description":"Size of the results array"},{"schema":{"type":"array","items":{"type":"string","enum":["user","team","roles"],"description":"Allowed expand paths: `user`, `team`, `roles`."}},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Accepts repeated params (`expand=user&expand=roles`) or comma-separated (`expand=user,roles`)."},{"schema":{"type":"string"},"in":"path","name":"team_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"PaginatedResponse","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"type":"string","description":"next cursor","nullable":true},"results":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"},"description":"List of results"}},"description":"PaginatedResponse","additionalProperties":false}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"team/addMembers","summary":"Add Users to a Team","tags":["team"],"description":"Adds one or more Users of this tenant as members of the given Team, with the same roles. Users already in the team are skipped; `already_member` only when all of them are. A user or team that is not in this tenant is a 404. Returns the TeamMember record of the first user in `users`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["users"],"properties":{"users":{"type":"array","items":{"type":"string"},"description":"List of User ids to add as members of the team. Users already in the team are skipped.","example":["user_6555fd293acc2f0fac0e3452"]},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids to grant to the added members within the scope of this team.","default":[],"example":["role_6555fd293acc2f0fac0e3452"]}},"additionalProperties":false,"description":"Payload for adding one or more users to a team in a single call. Posted to `POST /team/:team_id/member`."}}},"description":"Payload for adding one or more users to a team in a single call. Posted to `POST /team/:team_id/member`."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"team_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"TeamMember","content":{"application/json":{"schema":{"type":"object","required":["id","user","team","roles","account","createdAt"],"properties":{"id":{"type":"string","description":"TeamMember ID"},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}],"description":"User that belongs to the team. Returned as an id by default; pass `?expand=user` to inline the full User object."},"team":{"anyOf":[{"$ref":"#/components/schemas/Team"},{"type":"string"},{}],"description":"Team the user belongs to. Returned as an id by default; pass `?expand=team` to inline the full Team object."},"roles":{"type":"array","items":{"anyOf":[{"$ref":"#/components/schemas/Role"},{"type":"string"},{}]},"description":"Roles granted to the user *within this team*. Distinct from account-wide RoleMember grants — these only apply in the scope of the team. Each entry is an id by default; pass `?expand=roles` to inline."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMemberMetadata","default":{}},"createdAt":{"type":"string","description":"TeamMember creation date"},"updatedAt":{"type":"string","description":"TeamMember updated date"}},"description":"TeamMember"}}}},"400":{"description":"`already_member` — The user is already a member of the team.\n\n`invalid_role` — The role belongs to another account.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_member","invalid_role","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/team/{team_id}/member/check/{user_id}":{"get":{"operationId":"team/checkMember","summary":"Check User is member of a Team","tags":["team"],"description":"Returns the TeamMember record if the User belongs to the given Team, or 404 otherwise.","parameters":[{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Start from this cursor"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Start from this cursor"},{"schema":{"type":"number"},"in":"query","name":"pageSize","required":false,"description":"Size of the results array"},{"schema":{"type":"string"},"in":"path","name":"team_id","required":true},{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"TeamMember","content":{"application/json":{"schema":{"type":"object","required":["id","user","team","roles","account","createdAt"],"properties":{"id":{"type":"string","description":"TeamMember ID"},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}],"description":"User that belongs to the team. Returned as an id by default; pass `?expand=user` to inline the full User object."},"team":{"anyOf":[{"$ref":"#/components/schemas/Team"},{"type":"string"},{}],"description":"Team the user belongs to. Returned as an id by default; pass `?expand=team` to inline the full Team object."},"roles":{"type":"array","items":{"anyOf":[{"$ref":"#/components/schemas/Role"},{"type":"string"},{}]},"description":"Roles granted to the user *within this team*. Distinct from account-wide RoleMember grants — these only apply in the scope of the team. Each entry is an id by default; pass `?expand=roles` to inline."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMemberMetadata","default":{}},"createdAt":{"type":"string","description":"TeamMember creation date"},"updatedAt":{"type":"string","description":"TeamMember updated date"}},"description":"TeamMember"}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_a_member` — The user is not a member of the team.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_a_member","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/team/{team_id}/member/{user_id}":{"post":{"operationId":"team/updateMember","summary":"Change a Team member's roles","tags":["team"],"description":"Replaces the roles a User holds within the given Team. Every role must be a Role of this tenant. Emits `team.member.updated` when the roles actually change, and leaves an entry in the tenant's log.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["roles"],"properties":{"roles":{"type":"array","items":{"type":"string"},"description":"Role ids the member will hold within this team. Replaces the current list entirely; `[]` leaves the member without roles.","example":["role_6555fd293acc2f0fac0e3452"]},"actor_user_id":{"type":"string","description":"The person making the change, when your backend calls on their behalf with a machine token: it is who the tenant's log names. Must be a user of this tenant."}},"additionalProperties":false,"description":"Payload for replacing the roles of one team member."}}},"description":"Payload for replacing the roles of one team member."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"team_id","required":true},{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"TeamMember","content":{"application/json":{"schema":{"type":"object","required":["id","user","team","roles","account","createdAt"],"properties":{"id":{"type":"string","description":"TeamMember ID"},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}],"description":"User that belongs to the team. Returned as an id by default; pass `?expand=user` to inline the full User object."},"team":{"anyOf":[{"$ref":"#/components/schemas/Team"},{"type":"string"},{}],"description":"Team the user belongs to. Returned as an id by default; pass `?expand=team` to inline the full Team object."},"roles":{"type":"array","items":{"anyOf":[{"$ref":"#/components/schemas/Role"},{"type":"string"},{}]},"description":"Roles granted to the user *within this team*. Distinct from account-wide RoleMember grants — these only apply in the scope of the team. Each entry is an id by default; pass `?expand=roles` to inline."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMemberMetadata","default":{}},"createdAt":{"type":"string","description":"TeamMember creation date"},"updatedAt":{"type":"string","description":"TeamMember updated date"}},"description":"TeamMember"}}}},"400":{"description":"`invalid_role` — The role belongs to another account.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_role","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_a_member` — The user is not a member of the team.\n\n`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_a_member","not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"team/removeMember","summary":"Remove a User from a Team","tags":["team"],"description":"Removes the User's membership of the given Team (the User itself is not deleted). Emits `team.member.removed` and leaves an entry in the tenant's log.","parameters":[{"schema":{"type":"string"},"in":"query","name":"actor_user_id","required":false,"description":"The person removing (or leaving), when your backend calls on their behalf with a machine token: it is who the tenant's log names. Must be a user of this tenant."},{"schema":{"type":"string"},"in":"path","name":"team_id","required":true},{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["id","deleted"],"properties":{"id":{"type":"string","description":"Id of the removed TeamMember record."},"deleted":{"type":"boolean","enum":[true]}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_a_member` — The user is not a member of the team.\n\n`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_a_member","not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/team/{team_id}/invite":{"post":{"operationId":"team/invite","summary":"Invite a user to a Team by email","tags":["team"],"description":"Invites a user by email. `mode='auto'` (default) adds the user directly when the email already corresponds to a user in this tenant and falls back to a ticketed invite + email otherwise. `mode='invite'` always issues a ticket + email even when the user exists. Re-inviting the same email to the same team invalidates the previous pending invite.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","description":"Email of the user to invite to the team.","example":"someone@example.com"},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids granted to the resulting TeamMember within the scope of this team.","default":[]},"mode":{"anyOf":[{"type":"string","enum":["auto"]},{"type":"string","enum":["invite"]}],"description":"`auto` adds the user directly if they already exist in the tenant and only falls back to a ticketed invite when the email is unknown. `invite` always issues a ticket + email regardless of whether the user exists."},"redirect_uri":{"type":"string","description":"Where the invitee goes once in: `/invite-verify` redirects there after a successful accept, and it is the button of the \"you were added\" email (otherwise your auth domain). Must be at an origin of one of this tenant's client callbacks or `callback_hostnames`."},"inviter_user_id":{"type":"string","description":"The user who is inviting, when you call on their behalf with a machine token. Shown in the invite email. Must be a user of this tenant; defaults to the token's own user."},"accept_url":{"type":"string","description":"Your own page for accepting the invite. The emailed link becomes `<accept_url>#<token>`; the page reads the token from the fragment, signs the user in, and calls `POST /team/invite/accept` with the token and the signed-in user's id. Must be at an origin of one of this tenant's client callbacks or `callback_hostnames`. Without it the link is auth's own `/invite-verify`."}},"additionalProperties":false,"description":"Payload for inviting a user to a team by email. `mode='auto'` (default) adds the user immediately if they already exist in the tenant and falls back to a ticketed invite otherwise. `mode='invite'` always issues a ticket + email."}}},"description":"Payload for inviting a user to a team by email. `mode='auto'` (default) adds the user immediately if they already exist in the tenant and falls back to a ticketed invite otherwise. `mode='invite'` always issues a ticket + email."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"team_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"anyOf":[{"type":"string","enum":["invited"]},{"type":"string","enum":["added"]}],"description":"`invited` when a ticket + email was issued, `added` when the user was added directly (auto mode + existing user)."},"ticket_id":{"type":"string","description":"ID of the issued invite ticket (when status=invited)."},"member":{"type":"object","required":["id","user","team","roles","account","createdAt"],"properties":{"id":{"type":"string","description":"TeamMember ID"},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}],"description":"User that belongs to the team. Returned as an id by default; pass `?expand=user` to inline the full User object."},"team":{"anyOf":[{"$ref":"#/components/schemas/Team"},{"type":"string"},{}],"description":"Team the user belongs to. Returned as an id by default; pass `?expand=team` to inline the full Team object."},"roles":{"type":"array","items":{"anyOf":[{"$ref":"#/components/schemas/Role"},{"type":"string"},{}]},"description":"Roles granted to the user *within this team*. Distinct from account-wide RoleMember grants — these only apply in the scope of the team. Each entry is an id by default; pass `?expand=roles` to inline."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamMemberMetadata","default":{}},"createdAt":{"type":"string","description":"TeamMember creation date"},"updatedAt":{"type":"string","description":"TeamMember updated date"}},"description":"TeamMember"}},"additionalProperties":false}}}},"400":{"description":"`invite_used` — The invite was already accepted.\n\n`invalid_role` — The role belongs to another account.\n\n`invalid_invite_url` — An invite's `accept_url` or `redirect_uri` is not an http(s) URL at an origin of this tenant's client callbacks or `callback_hostnames`, or it carries a fragment.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invite_used","invalid_role","invalid_invite_url","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"get":{"operationId":"team/listInvites","summary":"List pending invites for a Team","tags":["team"],"description":"Returns all pending (not consumed, not expired) team invites for the given team. Accepts a FaableQL `query` parameter on the `email` field.","parameters":[{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"FaableQL filter. Supported fields: `email`. Example: `?query=email:foo@bar.com`"},{"schema":{"type":"string"},"in":"path","name":"team_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","email","team","roles","mode","expires_at","account","createdAt"],"properties":{"id":{"type":"string","description":"TeamInvite ID"},"email":{"type":"string","description":"Email address that was invited"},"team":{"type":"string","description":"Id of the team the invitee will be added to upon acceptance."},"inviter":{"type":"string","description":"Id of the user that created the invitation, if known."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids that will be applied to the new TeamMember on acceptance."},"mode":{"anyOf":[{"type":"string","enum":["auto"]},{"type":"string","enum":["invite"]}],"description":"`auto` adds the user directly if they already exist in the tenant and only falls back to a ticketed invite when the email is unknown. `invite` always issues a ticket + email regardless of whether the user exists."},"redirect_uri":{"type":"string","description":"URI to redirect the invitee to after a successful accept."},"expires_at":{"type":"string","description":"ISO 8601 timestamp at which the invite stops being valid."},"consume_date":{"type":"string","description":"ISO 8601 timestamp at which the invite was accepted. Absent for pending invites."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamInviteMetadata","default":{}},"createdAt":{"type":"string","description":"TeamInvite creation date"},"updatedAt":{"type":"string","description":"TeamInvite updated date"}},"description":"TeamInvite"}}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/team/{team_id}/invite/{ticket_id}":{"delete":{"operationId":"team/revokeInvite","summary":"Revoke a pending team invite","tags":["team"],"description":"Marks a pending team invite as consumed so it can no longer be accepted. Returns 400 if the invite is already consumed and 404 if it does not exist or belongs to a different team.","parameters":[{"schema":{"type":"string"},"in":"path","name":"team_id","required":true},{"schema":{"type":"string"},"in":"path","name":"ticket_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["revoked"]}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`invite_not_found` — No invite with that id in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invite_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/invite-verify":{"get":{"operationId":"team_invite_verify","summary":"Verify a team-invite ticket and add the user to the team","tags":["team"],"description":"Entry point for the link sent to the invitee, which carries `token` — the invite's own secret. `ticket` (the invite id) is accepted only for invites issued before invites had a secret. Consumes the ticket, creates the user if needed (with `email_verified=true` — the click proves ownership), and adds them to the team with the invited roles. Then redirects to the `redirect_uri` supplied at invite time with `?status=accepted` appended; falls back to `/flow/team-invite-done` on the auth host when no `redirect_uri` was given. Idempotent if the user is already a member.","parameters":[{"schema":{"type":"string","minLength":1},"in":"query","name":"token","required":false},{"schema":{"type":"string","minLength":1},"in":"query","name":"ticket","required":false}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_ticket` — The ticket does not exist or is not the kind this endpoint accepts.\n\n`ticket_expired` — The ticket is past its expiry.\n\n`ticket_used` — The ticket was already consumed.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_ticket","ticket_expired","ticket_used","validation_error"]}}}]}}}},"404":{"description":"`invite_not_found` — No invite with that id in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invite_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/team/invite/accept":{"post":{"operationId":"team/acceptInvite","summary":"Accept a team invite for a signed-in user","tags":["team"],"description":"Accepts the invite — by the `token` of its link, or by `invite_id` — on behalf of `user_id`, an existing user of this tenant. The user's email must be verified and equal to the invited one (`invite_email_mismatch` otherwise, with the invited address masked in `details`). Never creates a user. Use `token` from the page you gave as the invite's `accept_url`, and `invite_id` from a pending-invitation notice in your app.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_id"],"properties":{"token":{"type":"string","minLength":1,"description":"The invite secret, as it arrived in the fragment of the `accept_url` link. Give this or `invite_id`."},"invite_id":{"type":"string","minLength":1,"description":"The invite id, for accepting from your app without the link (a \"pending invitation\" notice; see `GET /user/{user_id}/team-invites`). Give this or `token`."},"user_id":{"type":"string","description":"The user accepting — the one signed in on your page. Must exist in this tenant and have the invited email, verified."}},"additionalProperties":false}}}},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["status","team_id","user_id","roles"],"properties":{"status":{"anyOf":[{"type":"string","enum":["accepted"]},{"type":"string","enum":["already_member"]}],"description":"`accepted` when the user joined now; `already_member` when they already were (the invite is spent either way)."},"team_id":{"type":"string"},"user_id":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}}},"400":{"description":"`invalid_ticket` — The ticket does not exist or is not the kind this endpoint accepts.\n\n`ticket_used` — The ticket was already consumed.\n\n`ticket_expired` — The ticket is past its expiry.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_ticket","ticket_used","ticket_expired","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`invite_email_mismatch` — The invite is for another email address than the accepting user's. `details.invited_email` shows it masked.\n\n`invite_email_unverified` — The accepting user's email matches the invite but is not verified yet.\n\n`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invite_email_mismatch","invite_email_unverified","forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/user/{user_id}/team-invites":{"get":{"operationId":"team/listInvitesForUser","summary":"List a user's pending team invites","tags":["team"],"description":"Pending (not accepted, revoked, declined or expired) invites to any team of this tenant addressed to the user's email. Only when that email is verified; otherwise `email_verified: false` and an empty list.","parameters":[{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["email_verified","data"],"properties":{"email_verified":{"type":"boolean","description":"False when the user's email is not verified: nothing is listed then, since the address could be anybody's."},"data":{"type":"array","items":{"type":"object","required":["id","email","team","roles","mode","expires_at","account","createdAt"],"properties":{"id":{"type":"string","description":"TeamInvite ID"},"email":{"type":"string","description":"Email address that was invited"},"team":{"type":"string","description":"Id of the team the invitee will be added to upon acceptance."},"inviter":{"type":"string","description":"Id of the user that created the invitation, if known."},"roles":{"type":"array","items":{"type":"string"},"description":"Role ids that will be applied to the new TeamMember on acceptance."},"mode":{"anyOf":[{"type":"string","enum":["auto"]},{"type":"string","enum":["invite"]}],"description":"`auto` adds the user directly if they already exist in the tenant and only falls back to a ticketed invite when the email is unknown. `invite` always issues a ticket + email regardless of whether the user exists."},"redirect_uri":{"type":"string","description":"URI to redirect the invitee to after a successful accept."},"expires_at":{"type":"string","description":"ISO 8601 timestamp at which the invite stops being valid."},"consume_date":{"type":"string","description":"ISO 8601 timestamp at which the invite was accepted. Absent for pending invites."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"TeamInviteMetadata","default":{}},"createdAt":{"type":"string","description":"TeamInvite creation date"},"updatedAt":{"type":"string","description":"TeamInvite updated date"}},"description":"TeamInvite"}}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/team/invite/decline":{"post":{"operationId":"team/declineInvite","summary":"Decline a team invite for a signed-in user","tags":["team"],"description":"The invited user says no: the invite is spent (its link stops working) and `team.invite.revoked` is emitted with reason `declined`. Same checks as accepting.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["invite_id","user_id"],"properties":{"invite_id":{"type":"string","minLength":1},"user_id":{"type":"string","description":"The user declining — must have the invited email, verified."}},"additionalProperties":false}}}},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["declined"]}}}}}},"400":{"description":"`invalid_ticket` — The ticket does not exist or is not the kind this endpoint accepts.\n\n`ticket_used` — The ticket was already consumed.\n\n`ticket_expired` — The ticket is past its expiry.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_ticket","ticket_used","ticket_expired","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`invite_email_mismatch` — The invite is for another email address than the accepting user's. `details.invited_email` shows it masked.\n\n`invite_email_unverified` — The accepting user's email matches the invite but is not verified yet.\n\n`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invite_email_mismatch","invite_email_unverified","forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/api":{"get":{"operationId":"api/list","summary":"List Apis","tags":["api"],"description":"List Apis","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"string"},"in":"query","name":"name","required":false,"description":"Exact match on `name`. Equivalent to `?query=name:<value>`."},{"schema":{"type":"string"},"in":"query","name":"slug","required":false,"description":"Exact match on `slug`. Equivalent to `?query=slug:<value>`."},{"schema":{"type":"string"},"in":"query","name":"identifier","required":false,"description":"Exact match on `identifier`. Equivalent to `?query=identifier:<value>`."},{"schema":{"type":"string"},"in":"query","name":"token_dialect","required":false,"description":"Exact match on `token_dialect`. Equivalent to `?query=token_dialect:<value>`."},{"schema":{"type":"string"},"in":"query","name":"enforce_policies","required":false,"description":"Exact match on `enforce_policies`. Equivalent to `?query=enforce_policies:<value>`."},{"schema":{"type":"string"},"in":"query","name":"rbac","required":false,"description":"Exact match on `rbac`. Equivalent to `?query=rbac:<value>`."},{"schema":{"type":"string"},"in":"query","name":"allow_offline_access","required":false,"description":"Exact match on `allow_offline_access`. Equivalent to `?query=allow_offline_access:<value>`."},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"query","name":"q","required":false,"description":"Full-text search across: `name`, `description`, `identifier`, `slug`."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Api"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"api/create","summary":"Create Api","tags":["api"],"description":"Create Api","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","identifier"],"properties":{"name":{"type":"string","description":"Human-readable name for the API.","example":"My Backend API"},"identifier":{"type":"string","description":"Audience URL clients pass as `audience=` when requesting a token for this API. Must be unique within the tenant and is immutable after creation.","example":"https://api.faable.com"},"description":{"type":"string","description":"Optional human-readable description of the API."},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/PermissionCreate"},"description":"Set of permissions (scopes) this API exposes. Tokens issued with `audience=identifier` will only carry these in their `permissions` claim.","default":[]},"signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign access_tokens issued for this API. Currently only RS256 (the account keystore default) is implemented."},"token_dialect":{"anyOf":[{"type":"string","enum":["access_token"]},{"type":"string","enum":["access_token_authz"]}],"description":"Shape of access_tokens issued for this API. `access_token` emits the standard OAuth2 token. `access_token_authz` additionally embeds a `permissions` claim listing the permissions granted to the subject for this API."},"token_lifetime":{"type":"integer","description":"Access_token lifetime in seconds. Defaults to 86400 (24h) if omitted.","minimum":60,"maximum":2592000},"enforce_policies":{"type":"boolean","description":"When true, requested scopes are filtered against `permissions` at token issuance."},"rbac":{"type":"boolean","description":"When true, user tokens carry only the permissions granted to the user's roles. Defaults to false."},"allow_offline_access":{"type":"boolean","description":"When true, clients can request `offline_access` and receive a refresh_token for this API."},"consent_options":{"type":"object","required":["claim","label","url","audience"],"properties":{"claim":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,31}$","description":"Access token claim that carries the chosen id."},"label":{"type":"string","maxLength":60,"description":"What the screen asks for, e.g. \"Project\"."},"url":{"type":"string","pattern":"^https://","description":"GET endpoint of the resource server that lists the choices."},"audience":{"type":"string","description":"Identifier of a registered API the listing token is issued for (usually the resource server behind `url`)."},"scope":{"type":"string","description":"Space-separated permissions of the listing token. The token is delegated (`act`): the resource server sees auth acting for the user with only these, never the user’s full powers — an admin is listed their own choices, not the whole platform’s."}}},"delegation":{"type":"object","required":["client_id","audience"],"properties":{"client_id":{"type":"string","description":"The confidential client of the resource server allowed to exchange."},"audience":{"type":"string","description":"Identifier of the registered API the new token is for."}}},"skip_consent":{"type":"boolean","description":"When true, skip the consent prompt for first-party clients."},"include_teams_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `teams` claim with the subject team slugs."},"include_roles_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `roles` claim with the subject role names."},"include_teams_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `teams` claim with the subject team slugs."},"include_roles_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `roles` claim with the subject role names."}},"additionalProperties":false,"description":"Payload for creating an Api (a.k.a. Resource Server / Audience). The `identifier` becomes the `aud` claim in tokens issued for this API and cannot be changed after creation."}}},"description":"Payload for creating an Api (a.k.a. Resource Server / Audience). The `identifier` becomes the `aud` claim in tokens issued for this API and cannot be changed after creation."},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Api","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","identifier","permissions","signing_alg","token_dialect","token_lifetime","enforce_policies","rbac","allow_offline_access","skip_consent","include_teams_in_access_token","include_roles_in_access_token","include_teams_in_id_token","include_roles_in_id_token","account","createdAt"],"properties":{"id":{"type":"string","description":"Api ID"},"name":{"type":"string","description":"Human-readable name for the API. Shown in the dashboard and consent prompts.","example":"My Backend API"},"slug":{"type":"string","description":"URL-safe identifier auto-derived from `name`. Unique within the tenant."},"identifier":{"type":"string","description":"Audience URL that clients pass as `audience=` when requesting a token for this API. Becomes the `aud` claim in the issued access_token. Immutable after creation and unique within the tenant.","example":"https://api.faable.com"},"description":{"type":"string","description":"Optional human-readable description of the API."},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"},"description":"Set of permissions (scopes) this API exposes. When a token is issued with `audience=identifier`, the requested `scope` is intersected with this list to populate the `permissions` claim."},"signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign access_tokens issued for this API. Currently only RS256 (the account keystore default) is implemented."},"token_dialect":{"anyOf":[{"type":"string","enum":["access_token"]},{"type":"string","enum":["access_token_authz"]}],"description":"Shape of access_tokens issued for this API. `access_token` emits the standard OAuth2 token. `access_token_authz` additionally embeds a `permissions` claim listing the permissions granted to the subject for this API."},"token_lifetime":{"type":"integer","description":"Access_token lifetime in seconds. Default 86400 (24h). Applied at token issuance time (overrides the generic default).","minimum":60,"maximum":2592000},"enforce_policies":{"type":"boolean","description":"When true, only scopes that match a permission in `permissions` are forwarded into the access_token. When false, requested scopes are echoed back verbatim with no filtering."},"rbac":{"type":"boolean","description":"Role-based access control. When true, a token issued to a user carries only the permissions of this API granted to the user's roles (account-level role assignments, not team roles): `permissions` lists all of them, and `scope` keeps only the requested ones the user holds. Machine-to-machine tokens (`client_credentials`) are not affected. The `permissions` claim requires `token_dialect: access_token_authz`."},"allow_offline_access":{"type":"boolean","description":"When true, the API is eligible to be the target of a `refresh_token` (i.e. clients can request `offline_access` against it)."},"consent_options":{"type":"object","required":["claim","label","url","audience"],"properties":{"claim":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,31}$","description":"Access token claim that carries the chosen id."},"label":{"type":"string","maxLength":60,"description":"What the screen asks for, e.g. \"Project\"."},"url":{"type":"string","pattern":"^https://","description":"GET endpoint of the resource server that lists the choices."},"audience":{"type":"string","description":"Identifier of a registered API the listing token is issued for (usually the resource server behind `url`)."},"scope":{"type":"string","description":"Space-separated permissions of the listing token. The token is delegated (`act`): the resource server sees auth acting for the user with only these, never the user’s full powers — an admin is listed their own choices, not the whole platform’s."}},"nullable":true},"delegation":{"type":"object","required":["client_id","audience"],"properties":{"client_id":{"type":"string","description":"The confidential client of the resource server allowed to exchange."},"audience":{"type":"string","description":"Identifier of the registered API the new token is for."}},"nullable":true},"skip_consent":{"type":"boolean","description":"When true, the consent prompt is skipped for first-party clients (clients owned by the same tenant as the API)."},"include_teams_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `teams` claim listing the slugs of the teams the subject is a member of within the tenant."},"include_roles_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `roles` claim listing the names of the roles the subject holds across their team memberships within the tenant."},"include_teams_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `teams` claim with the subject team slugs. The id_token only sees the flag when the client requested `audience=` so this API is resolved at issuance time."},"include_roles_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `roles` claim with the subject role names. Same audience caveat as `include_teams_in_id_token`."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ApiMetadata","default":{}},"createdAt":{"type":"string","description":"Api creation date"},"updatedAt":{"type":"string","description":"Api updated date"}},"description":"Api"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/api/{api_id}":{"get":{"operationId":"api/get","summary":"Get Api","tags":["api"],"description":"Get Api","parameters":[{"schema":{"type":"string"},"in":"path","name":"api_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Api","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","identifier","permissions","signing_alg","token_dialect","token_lifetime","enforce_policies","rbac","allow_offline_access","skip_consent","include_teams_in_access_token","include_roles_in_access_token","include_teams_in_id_token","include_roles_in_id_token","account","createdAt"],"properties":{"id":{"type":"string","description":"Api ID"},"name":{"type":"string","description":"Human-readable name for the API. Shown in the dashboard and consent prompts.","example":"My Backend API"},"slug":{"type":"string","description":"URL-safe identifier auto-derived from `name`. Unique within the tenant."},"identifier":{"type":"string","description":"Audience URL that clients pass as `audience=` when requesting a token for this API. Becomes the `aud` claim in the issued access_token. Immutable after creation and unique within the tenant.","example":"https://api.faable.com"},"description":{"type":"string","description":"Optional human-readable description of the API."},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"},"description":"Set of permissions (scopes) this API exposes. When a token is issued with `audience=identifier`, the requested `scope` is intersected with this list to populate the `permissions` claim."},"signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign access_tokens issued for this API. Currently only RS256 (the account keystore default) is implemented."},"token_dialect":{"anyOf":[{"type":"string","enum":["access_token"]},{"type":"string","enum":["access_token_authz"]}],"description":"Shape of access_tokens issued for this API. `access_token` emits the standard OAuth2 token. `access_token_authz` additionally embeds a `permissions` claim listing the permissions granted to the subject for this API."},"token_lifetime":{"type":"integer","description":"Access_token lifetime in seconds. Default 86400 (24h). Applied at token issuance time (overrides the generic default).","minimum":60,"maximum":2592000},"enforce_policies":{"type":"boolean","description":"When true, only scopes that match a permission in `permissions` are forwarded into the access_token. When false, requested scopes are echoed back verbatim with no filtering."},"rbac":{"type":"boolean","description":"Role-based access control. When true, a token issued to a user carries only the permissions of this API granted to the user's roles (account-level role assignments, not team roles): `permissions` lists all of them, and `scope` keeps only the requested ones the user holds. Machine-to-machine tokens (`client_credentials`) are not affected. The `permissions` claim requires `token_dialect: access_token_authz`."},"allow_offline_access":{"type":"boolean","description":"When true, the API is eligible to be the target of a `refresh_token` (i.e. clients can request `offline_access` against it)."},"consent_options":{"type":"object","required":["claim","label","url","audience"],"properties":{"claim":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,31}$","description":"Access token claim that carries the chosen id."},"label":{"type":"string","maxLength":60,"description":"What the screen asks for, e.g. \"Project\"."},"url":{"type":"string","pattern":"^https://","description":"GET endpoint of the resource server that lists the choices."},"audience":{"type":"string","description":"Identifier of a registered API the listing token is issued for (usually the resource server behind `url`)."},"scope":{"type":"string","description":"Space-separated permissions of the listing token. The token is delegated (`act`): the resource server sees auth acting for the user with only these, never the user’s full powers — an admin is listed their own choices, not the whole platform’s."}},"nullable":true},"delegation":{"type":"object","required":["client_id","audience"],"properties":{"client_id":{"type":"string","description":"The confidential client of the resource server allowed to exchange."},"audience":{"type":"string","description":"Identifier of the registered API the new token is for."}},"nullable":true},"skip_consent":{"type":"boolean","description":"When true, the consent prompt is skipped for first-party clients (clients owned by the same tenant as the API)."},"include_teams_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `teams` claim listing the slugs of the teams the subject is a member of within the tenant."},"include_roles_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `roles` claim listing the names of the roles the subject holds across their team memberships within the tenant."},"include_teams_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `teams` claim with the subject team slugs. The id_token only sees the flag when the client requested `audience=` so this API is resolved at issuance time."},"include_roles_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `roles` claim with the subject role names. Same audience caveat as `include_teams_in_id_token`."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ApiMetadata","default":{}},"createdAt":{"type":"string","description":"Api creation date"},"updatedAt":{"type":"string","description":"Api updated date"}},"description":"Api"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"api/update","summary":"Update Api","tags":["api"],"description":"Update Api","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable name for the API."},"description":{"type":"string","description":"Optional human-readable description of the API."},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/PermissionCreate"},"description":"Replaces the full permissions list. Tokens issued AFTER the update will use the new list; tokens already in circulation are unchanged."},"signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign access_tokens issued for this API. Currently only RS256 (the account keystore default) is implemented."},"token_dialect":{"anyOf":[{"type":"string","enum":["access_token"]},{"type":"string","enum":["access_token_authz"]}],"description":"Shape of access_tokens issued for this API. `access_token` emits the standard OAuth2 token. `access_token_authz` additionally embeds a `permissions` claim listing the permissions granted to the subject for this API."},"token_lifetime":{"type":"integer","description":"Access_token lifetime in seconds.","minimum":60,"maximum":2592000},"enforce_policies":{"type":"boolean","description":"When true, requested scopes are filtered against `permissions` at token issuance."},"rbac":{"type":"boolean","description":"When true, user tokens carry only the permissions granted to the user's roles. Applies to tokens issued after the update, including refreshes."},"allow_offline_access":{"type":"boolean","description":"When true, clients can request `offline_access` and receive a refresh_token for this API."},"consent_options":{"type":"object","required":["claim","label","url","audience"],"properties":{"claim":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,31}$","description":"Access token claim that carries the chosen id."},"label":{"type":"string","maxLength":60,"description":"What the screen asks for, e.g. \"Project\"."},"url":{"type":"string","pattern":"^https://","description":"GET endpoint of the resource server that lists the choices."},"audience":{"type":"string","description":"Identifier of a registered API the listing token is issued for (usually the resource server behind `url`)."},"scope":{"type":"string","description":"Space-separated permissions of the listing token. The token is delegated (`act`): the resource server sees auth acting for the user with only these, never the user’s full powers — an admin is listed their own choices, not the whole platform’s."}},"nullable":true},"delegation":{"type":"object","required":["client_id","audience"],"properties":{"client_id":{"type":"string","description":"The confidential client of the resource server allowed to exchange."},"audience":{"type":"string","description":"Identifier of the registered API the new token is for."}},"nullable":true},"skip_consent":{"type":"boolean","description":"When true, skip the consent prompt for first-party clients."},"include_teams_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `teams` claim with the subject team slugs."},"include_roles_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `roles` claim with the subject role names."},"include_teams_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `teams` claim with the subject team slugs."},"include_roles_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `roles` claim with the subject role names."}},"additionalProperties":false,"description":"Partial update for an Api. `identifier` is immutable and not present here; recreate the API if you need to change it."}}},"description":"Partial update for an Api. `identifier` is immutable and not present here; recreate the API if you need to change it."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"api_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Api","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","identifier","permissions","signing_alg","token_dialect","token_lifetime","enforce_policies","rbac","allow_offline_access","skip_consent","include_teams_in_access_token","include_roles_in_access_token","include_teams_in_id_token","include_roles_in_id_token","account","createdAt"],"properties":{"id":{"type":"string","description":"Api ID"},"name":{"type":"string","description":"Human-readable name for the API. Shown in the dashboard and consent prompts.","example":"My Backend API"},"slug":{"type":"string","description":"URL-safe identifier auto-derived from `name`. Unique within the tenant."},"identifier":{"type":"string","description":"Audience URL that clients pass as `audience=` when requesting a token for this API. Becomes the `aud` claim in the issued access_token. Immutable after creation and unique within the tenant.","example":"https://api.faable.com"},"description":{"type":"string","description":"Optional human-readable description of the API."},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"},"description":"Set of permissions (scopes) this API exposes. When a token is issued with `audience=identifier`, the requested `scope` is intersected with this list to populate the `permissions` claim."},"signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign access_tokens issued for this API. Currently only RS256 (the account keystore default) is implemented."},"token_dialect":{"anyOf":[{"type":"string","enum":["access_token"]},{"type":"string","enum":["access_token_authz"]}],"description":"Shape of access_tokens issued for this API. `access_token` emits the standard OAuth2 token. `access_token_authz` additionally embeds a `permissions` claim listing the permissions granted to the subject for this API."},"token_lifetime":{"type":"integer","description":"Access_token lifetime in seconds. Default 86400 (24h). Applied at token issuance time (overrides the generic default).","minimum":60,"maximum":2592000},"enforce_policies":{"type":"boolean","description":"When true, only scopes that match a permission in `permissions` are forwarded into the access_token. When false, requested scopes are echoed back verbatim with no filtering."},"rbac":{"type":"boolean","description":"Role-based access control. When true, a token issued to a user carries only the permissions of this API granted to the user's roles (account-level role assignments, not team roles): `permissions` lists all of them, and `scope` keeps only the requested ones the user holds. Machine-to-machine tokens (`client_credentials`) are not affected. The `permissions` claim requires `token_dialect: access_token_authz`."},"allow_offline_access":{"type":"boolean","description":"When true, the API is eligible to be the target of a `refresh_token` (i.e. clients can request `offline_access` against it)."},"consent_options":{"type":"object","required":["claim","label","url","audience"],"properties":{"claim":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,31}$","description":"Access token claim that carries the chosen id."},"label":{"type":"string","maxLength":60,"description":"What the screen asks for, e.g. \"Project\"."},"url":{"type":"string","pattern":"^https://","description":"GET endpoint of the resource server that lists the choices."},"audience":{"type":"string","description":"Identifier of a registered API the listing token is issued for (usually the resource server behind `url`)."},"scope":{"type":"string","description":"Space-separated permissions of the listing token. The token is delegated (`act`): the resource server sees auth acting for the user with only these, never the user’s full powers — an admin is listed their own choices, not the whole platform’s."}},"nullable":true},"delegation":{"type":"object","required":["client_id","audience"],"properties":{"client_id":{"type":"string","description":"The confidential client of the resource server allowed to exchange."},"audience":{"type":"string","description":"Identifier of the registered API the new token is for."}},"nullable":true},"skip_consent":{"type":"boolean","description":"When true, the consent prompt is skipped for first-party clients (clients owned by the same tenant as the API)."},"include_teams_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `teams` claim listing the slugs of the teams the subject is a member of within the tenant."},"include_roles_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `roles` claim listing the names of the roles the subject holds across their team memberships within the tenant."},"include_teams_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `teams` claim with the subject team slugs. The id_token only sees the flag when the client requested `audience=` so this API is resolved at issuance time."},"include_roles_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `roles` claim with the subject role names. Same audience caveat as `include_teams_in_id_token`."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ApiMetadata","default":{}},"createdAt":{"type":"string","description":"Api creation date"},"updatedAt":{"type":"string","description":"Api updated date"}},"description":"Api"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"api/delete","summary":"Delete Api","tags":["api"],"description":"Delete Api","parameters":[{"schema":{"type":"string"},"in":"path","name":"api_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Api","content":{"application/json":{"schema":{"type":"object","required":["id","name","slug","identifier","permissions","signing_alg","token_dialect","token_lifetime","enforce_policies","rbac","allow_offline_access","skip_consent","include_teams_in_access_token","include_roles_in_access_token","include_teams_in_id_token","include_roles_in_id_token","account","createdAt"],"properties":{"id":{"type":"string","description":"Api ID"},"name":{"type":"string","description":"Human-readable name for the API. Shown in the dashboard and consent prompts.","example":"My Backend API"},"slug":{"type":"string","description":"URL-safe identifier auto-derived from `name`. Unique within the tenant."},"identifier":{"type":"string","description":"Audience URL that clients pass as `audience=` when requesting a token for this API. Becomes the `aud` claim in the issued access_token. Immutable after creation and unique within the tenant.","example":"https://api.faable.com"},"description":{"type":"string","description":"Optional human-readable description of the API."},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"},"description":"Set of permissions (scopes) this API exposes. When a token is issued with `audience=identifier`, the requested `scope` is intersected with this list to populate the `permissions` claim."},"signing_alg":{"anyOf":[{"type":"string","enum":["RS256"]}],"description":"JWA algorithm used to sign access_tokens issued for this API. Currently only RS256 (the account keystore default) is implemented."},"token_dialect":{"anyOf":[{"type":"string","enum":["access_token"]},{"type":"string","enum":["access_token_authz"]}],"description":"Shape of access_tokens issued for this API. `access_token` emits the standard OAuth2 token. `access_token_authz` additionally embeds a `permissions` claim listing the permissions granted to the subject for this API."},"token_lifetime":{"type":"integer","description":"Access_token lifetime in seconds. Default 86400 (24h). Applied at token issuance time (overrides the generic default).","minimum":60,"maximum":2592000},"enforce_policies":{"type":"boolean","description":"When true, only scopes that match a permission in `permissions` are forwarded into the access_token. When false, requested scopes are echoed back verbatim with no filtering."},"rbac":{"type":"boolean","description":"Role-based access control. When true, a token issued to a user carries only the permissions of this API granted to the user's roles (account-level role assignments, not team roles): `permissions` lists all of them, and `scope` keeps only the requested ones the user holds. Machine-to-machine tokens (`client_credentials`) are not affected. The `permissions` claim requires `token_dialect: access_token_authz`."},"allow_offline_access":{"type":"boolean","description":"When true, the API is eligible to be the target of a `refresh_token` (i.e. clients can request `offline_access` against it)."},"consent_options":{"type":"object","required":["claim","label","url","audience"],"properties":{"claim":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,31}$","description":"Access token claim that carries the chosen id."},"label":{"type":"string","maxLength":60,"description":"What the screen asks for, e.g. \"Project\"."},"url":{"type":"string","pattern":"^https://","description":"GET endpoint of the resource server that lists the choices."},"audience":{"type":"string","description":"Identifier of a registered API the listing token is issued for (usually the resource server behind `url`)."},"scope":{"type":"string","description":"Space-separated permissions of the listing token. The token is delegated (`act`): the resource server sees auth acting for the user with only these, never the user’s full powers — an admin is listed their own choices, not the whole platform’s."}},"nullable":true},"delegation":{"type":"object","required":["client_id","audience"],"properties":{"client_id":{"type":"string","description":"The confidential client of the resource server allowed to exchange."},"audience":{"type":"string","description":"Identifier of the registered API the new token is for."}},"nullable":true},"skip_consent":{"type":"boolean","description":"When true, the consent prompt is skipped for first-party clients (clients owned by the same tenant as the API)."},"include_teams_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `teams` claim listing the slugs of the teams the subject is a member of within the tenant."},"include_roles_in_access_token":{"type":"boolean","description":"When true, access_tokens issued for this API carry a `roles` claim listing the names of the roles the subject holds across their team memberships within the tenant."},"include_teams_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `teams` claim with the subject team slugs. The id_token only sees the flag when the client requested `audience=` so this API is resolved at issuance time."},"include_roles_in_id_token":{"type":"boolean","description":"When true, id_tokens issued together with an access_token for this API carry a `roles` claim with the subject role names. Same audience caveat as `include_teams_in_id_token`."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ApiMetadata","default":{}},"createdAt":{"type":"string","description":"Api creation date"},"updatedAt":{"type":"string","description":"Api updated date"}},"description":"Api"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/account/keys/saml":{"get":{"operationId":"account/getSamlKeys","summary":"Get the SAML signing certificates","tags":["account"],"description":"The X.509 certificate this Account signs SAML assertions with, and the next one when a rotation is staged. Separate from the JWKS keys: rotating tokens never touches SAML. Created on first use.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["current"],"properties":{"current":{"type":"object","required":["id","certificate","not_after"],"properties":{"id":{"type":"string"},"certificate":{"type":"string","description":"PEM X.509 certificate"},"not_after":{"type":"string","format":"date-time"}},"additionalProperties":false},"next":{"type":"object","required":["id","certificate","not_after"],"properties":{"id":{"type":"string"},"certificate":{"type":"string","description":"PEM X.509 certificate"},"not_after":{"type":"string","format":"date-time"}},"additionalProperties":false}},"additionalProperties":false}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/account/keys/saml/rotate":{"post":{"operationId":"account/rotateSamlKeys","summary":"Rotate the SAML signing certificate","tags":["account"],"description":"Two steps. The first call stages a new certificate: the IdP metadata publishes both and signing continues with the current one, so each Service Provider can be updated. The second call promotes it: assertions are signed with the new certificate and the old one is dropped.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["step","current_id"],"properties":{"step":{"anyOf":[{"type":"string","enum":["staged"]},{"type":"string","enum":["promoted"]}]},"current_id":{"type":"string"},"next_id":{"type":"string"}},"additionalProperties":false}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/account/keys":{"get":{"operationId":"account/getKeys","summary":"Get Account signing keys info","tags":["account"],"description":"Returns metadata for the Account signing keys: the current production kid, the kid queued for next rotation, and all kids currently published in the JWK Set. Does not expose private key material.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["production_kid","next_in_queue_kid","kids_active"],"properties":{"production_kid":{"type":"string","description":"kid currently used to sign new tokens"},"next_in_queue_kid":{"type":"string","description":"kid that will be promoted on the next rotation"},"kids_active":{"type":"array","items":{"type":"string"},"description":"All kids currently published in the JWK Set"}},"additionalProperties":false}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/account/keys/rotate":{"post":{"operationId":"account/rotateKeys","summary":"Rotate Account signing keys","tags":["account"],"description":"Promotes the queued key to production and generates a fresh queued key. The previously-active key is retained inside the JWK Set so tokens already issued remain verifiable via `/.well-known/jwks.json`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false}}}},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["rotated_at","previous_production_kid","production_kid","next_in_queue_kid","kids_active"],"properties":{"rotated_at":{"type":"string","description":"ISO 8601 timestamp of the rotation"},"previous_production_kid":{"type":"string","description":"kid that was active before rotation. Tokens signed with it are still verifiable via /.well-known/jwks.json"},"production_kid":{"type":"string","description":"kid now used to sign new tokens"},"next_in_queue_kid":{"type":"string","description":"kid that will be promoted on the next rotation"},"kids_active":{"type":"array","items":{"type":"string"},"description":"All kids currently published in the JWK Set"}},"additionalProperties":false}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/action":{"get":{"operationId":"action/list","summary":"List Actions","tags":["action"],"description":"List Actions","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Action"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"action/create","summary":"Create Action","tags":["action"],"description":"Create Action","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","code"],"properties":{"name":{"type":"string","description":"Action Name","maxLength":200},"code":{"type":"string","description":"JavaScript code. Must export at least one hook: exports.onExecutePostLogin, exports.onExecuteContinue and/or exports.onExecuteClientCredentials. The triggers are derived from the exported hooks."},"enabled":{"type":"boolean","description":"Is this action active?","default":true},"order":{"type":"number","description":"Execution order: lower runs first; on ties the newest action runs first","default":0}}}}}},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Action","content":{"application/json":{"schema":{"type":"object","required":["id","name","triggers","code","enabled","account","createdAt"],"properties":{"id":{"type":"string","description":"Action ID"},"name":{"type":"string","description":"Action Name","maxLength":200},"triggers":{"type":"array","items":{"enum":["continue","post-login","client-credentials"]},"description":"Triggers this action runs on. Derived from the hooks the code exports (`onExecutePostLogin` → post-login, `onExecuteContinue` → continue, `onExecuteClientCredentials` → client-credentials); read-only."},"code":{"type":"string","description":"JavaScript Code to execute"},"enabled":{"type":"boolean","description":"Is this action active?","default":true},"order":{"type":"number","description":"Execution order: lower runs first; on ties the newest action runs first","default":0},"revision":{"type":"number","description":"Monotonic edit counter — increments on every update. Audit rows stamp the revision that produced each allow/deny (`data.action_revision`); `updatedAt` dates the current revision.","default":1},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ActionMetadata","default":{}},"createdAt":{"type":"string","description":"Action creation date"},"updatedAt":{"type":"string","description":"Action updated date"}},"description":"Action"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/action/{action_id}":{"get":{"operationId":"action/get","summary":"Get Action","tags":["action"],"description":"Get Action","parameters":[{"schema":{"type":"string"},"in":"path","name":"action_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Action","content":{"application/json":{"schema":{"type":"object","required":["id","name","triggers","code","enabled","account","createdAt"],"properties":{"id":{"type":"string","description":"Action ID"},"name":{"type":"string","description":"Action Name","maxLength":200},"triggers":{"type":"array","items":{"enum":["continue","post-login","client-credentials"]},"description":"Triggers this action runs on. Derived from the hooks the code exports (`onExecutePostLogin` → post-login, `onExecuteContinue` → continue, `onExecuteClientCredentials` → client-credentials); read-only."},"code":{"type":"string","description":"JavaScript Code to execute"},"enabled":{"type":"boolean","description":"Is this action active?","default":true},"order":{"type":"number","description":"Execution order: lower runs first; on ties the newest action runs first","default":0},"revision":{"type":"number","description":"Monotonic edit counter — increments on every update. Audit rows stamp the revision that produced each allow/deny (`data.action_revision`); `updatedAt` dates the current revision.","default":1},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ActionMetadata","default":{}},"createdAt":{"type":"string","description":"Action creation date"},"updatedAt":{"type":"string","description":"Action updated date"}},"description":"Action"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"action/update","summary":"Update Action","tags":["action"],"description":"Update Action","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Action Name","maxLength":200},"code":{"type":"string","description":"JavaScript code. The triggers are re-derived from the hooks it exports."},"enabled":{"type":"boolean","description":"Is this action active?"},"order":{"type":"number","description":"Execution order"}},"description":"Partial update for an Action. Only the supplied fields are modified; `triggers` is read-only and follows `code`.","additionalProperties":false}}},"description":"Partial update for an Action. Only the supplied fields are modified; `triggers` is read-only and follows `code`."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"action_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Action","content":{"application/json":{"schema":{"type":"object","required":["id","name","triggers","code","enabled","account","createdAt"],"properties":{"id":{"type":"string","description":"Action ID"},"name":{"type":"string","description":"Action Name","maxLength":200},"triggers":{"type":"array","items":{"enum":["continue","post-login","client-credentials"]},"description":"Triggers this action runs on. Derived from the hooks the code exports (`onExecutePostLogin` → post-login, `onExecuteContinue` → continue, `onExecuteClientCredentials` → client-credentials); read-only."},"code":{"type":"string","description":"JavaScript Code to execute"},"enabled":{"type":"boolean","description":"Is this action active?","default":true},"order":{"type":"number","description":"Execution order: lower runs first; on ties the newest action runs first","default":0},"revision":{"type":"number","description":"Monotonic edit counter — increments on every update. Audit rows stamp the revision that produced each allow/deny (`data.action_revision`); `updatedAt` dates the current revision.","default":1},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ActionMetadata","default":{}},"createdAt":{"type":"string","description":"Action creation date"},"updatedAt":{"type":"string","description":"Action updated date"}},"description":"Action"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"action/delete","summary":"Delete Action","tags":["action"],"description":"Delete Action","parameters":[{"schema":{"type":"string"},"in":"path","name":"action_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Action","content":{"application/json":{"schema":{"type":"object","required":["id","name","triggers","code","enabled","account","createdAt"],"properties":{"id":{"type":"string","description":"Action ID"},"name":{"type":"string","description":"Action Name","maxLength":200},"triggers":{"type":"array","items":{"enum":["continue","post-login","client-credentials"]},"description":"Triggers this action runs on. Derived from the hooks the code exports (`onExecutePostLogin` → post-login, `onExecuteContinue` → continue, `onExecuteClientCredentials` → client-credentials); read-only."},"code":{"type":"string","description":"JavaScript Code to execute"},"enabled":{"type":"boolean","description":"Is this action active?","default":true},"order":{"type":"number","description":"Execution order: lower runs first; on ties the newest action runs first","default":0},"revision":{"type":"number","description":"Monotonic edit counter — increments on every update. Audit rows stamp the revision that produced each allow/deny (`data.action_revision`); `updatedAt` dates the current revision.","default":1},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"ActionMetadata","default":{}},"createdAt":{"type":"string","description":"Action creation date"},"updatedAt":{"type":"string","description":"Action updated date"}},"description":"Action"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/loginflow/resolved":{"get":{"operationId":"loginflow/resolved","summary":"The graph this account (or client) runs right now","tags":["loginflow"],"description":"What a login actually walks: the published graph of the bound flow, or the graph compiled from the settings when nothing is bound or published. With `client_id`, resolved for that client (its own flow, else the account's, else compiled from its overrides). The bound flow, if any, comes along with its draft so an editor can show both.","parameters":[{"schema":{"type":"string"},"in":"query","name":"client_id","required":false}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["source","flow_id","revision","graph","bound"],"properties":{"source":{"anyOf":[{"type":"string","enum":["compiled"]},{"type":"string","enum":["published"]}]},"flow_id":{"anyOf":[{"type":"string"},{"type":"null"}]},"revision":{"type":"integer"},"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"bound":{"anyOf":[{"type":"object","required":["flow_id","bound_to","name","revision","published_revision","draft","history"],"properties":{"flow_id":{"type":"string"},"bound_to":{"anyOf":[{"type":"string","enum":["account"]},{"type":"string","enum":["client"]}]},"name":{"type":"string"},"revision":{"type":"integer"},"published_revision":{"anyOf":[{"type":"integer"},{"type":"null"}]},"draft":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"layout":{"type":"object","description":"Node positions in the editor. Presentation only.","additionalProperties":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}},"additionalProperties":false}},"history":{"type":"array","items":{"type":"integer"}}}},{"type":"null"}]}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/loginflow/materialize":{"post":{"operationId":"loginflow/materialize","summary":"Start a custom flow from what runs today","tags":["loginflow"],"description":"Creates a login flow whose draft is the graph the account (or, with `client_id`, that client) currently runs — compiled from its settings — and binds it. Nothing changes for logins until the flow is published: a bound flow with no published graph keeps running the compiled one. Never a blank canvas: customising always starts from the tenant's own behaviour.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"client_id":{"type":"string"},"name":{"type":"string","maxLength":200}}}}}},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"LoginFlow","content":{"application/json":{"schema":{"type":"object","required":["id","name","trigger","graph","revision","history","account","createdAt"],"properties":{"id":{"type":"string","description":"LoginFlow ID"},"name":{"type":"string","maxLength":200},"trigger":{"type":"string","enum":["login"]},"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer","description":"Monotonic edit counter, bumped on every update. A publish freezes the graph under the revision it had; a login in flight pins the revision it started on."},"published":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"history":{"type":"array","items":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"description":"Previously published graphs, newest first, for rollback."},"layout":{"type":"object","description":"Node positions in the editor. Presentation only.","additionalProperties":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}},"additionalProperties":false}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"LoginFlowMetadata","default":{}},"createdAt":{"type":"string","description":"LoginFlow creation date"},"updatedAt":{"type":"string","description":"LoginFlow updated date"}},"description":"LoginFlow"}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/loginflow/{loginflow_id}/validate":{"post":{"operationId":"loginflow/validate","summary":"Check a draft against the rules a publish enforces","tags":["loginflow"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"client_id":{"type":"string"}}}}}},"security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"loginflow_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["ok","issues"],"properties":{"ok":{"type":"boolean"},"issues":{"type":"array","items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"node":{"type":"string"},"handle":{"type":"string"},"message":{"type":"string"}}}}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/loginflow/{loginflow_id}/publish":{"post":{"operationId":"loginflow/publish","summary":"Make the draft the graph logins run","tags":["loginflow"],"description":"Validates the draft and freezes it as `published` under its current revision; the previous published graph moves to `history`. Logins already in flight keep the revision they started on. Refused with the list of issues when the draft does not validate.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"client_id":{"type":"string"}}}}}},"security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"loginflow_id","required":true}],"responses":{"200":{"description":"LoginFlow","content":{"application/json":{"schema":{"type":"object","required":["id","name","trigger","graph","revision","history","account","createdAt"],"properties":{"id":{"type":"string","description":"LoginFlow ID"},"name":{"type":"string","maxLength":200},"trigger":{"type":"string","enum":["login"]},"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer","description":"Monotonic edit counter, bumped on every update. A publish freezes the graph under the revision it had; a login in flight pins the revision it started on."},"published":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"history":{"type":"array","items":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"description":"Previously published graphs, newest first, for rollback."},"layout":{"type":"object","description":"Node positions in the editor. Presentation only.","additionalProperties":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}},"additionalProperties":false}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"LoginFlowMetadata","default":{}},"createdAt":{"type":"string","description":"LoginFlow creation date"},"updatedAt":{"type":"string","description":"LoginFlow updated date"}},"description":"LoginFlow"}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"422":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["error","issues"],"properties":{"error":{"type":"string"},"issues":{"type":"array","items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"node":{"type":"string"},"handle":{"type":"string"},"message":{"type":"string"}}}}}}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/loginflow/{loginflow_id}/rollback":{"post":{"operationId":"loginflow/rollback","summary":"Run a previously published revision again","tags":["loginflow"],"description":"Makes the named revision from `history` the published graph; the one being replaced moves to `history`. The draft is not touched.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["revision"],"properties":{"revision":{"type":"integer"}}}}}},"security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"loginflow_id","required":true}],"responses":{"200":{"description":"LoginFlow","content":{"application/json":{"schema":{"type":"object","required":["id","name","trigger","graph","revision","history","account","createdAt"],"properties":{"id":{"type":"string","description":"LoginFlow ID"},"name":{"type":"string","maxLength":200},"trigger":{"type":"string","enum":["login"]},"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer","description":"Monotonic edit counter, bumped on every update. A publish freezes the graph under the revision it had; a login in flight pins the revision it started on."},"published":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"history":{"type":"array","items":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"description":"Previously published graphs, newest first, for rollback."},"layout":{"type":"object","description":"Node positions in the editor. Presentation only.","additionalProperties":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}},"additionalProperties":false}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"LoginFlowMetadata","default":{}},"createdAt":{"type":"string","description":"LoginFlow creation date"},"updatedAt":{"type":"string","description":"LoginFlow updated date"}},"description":"LoginFlow"}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`revision_not_found` — The revision is not in the flow history.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["revision_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/loginflow/{loginflow_id}/preview-token":{"post":{"operationId":"loginflow/previewToken","summary":"Try the draft on a real login, in your browser only","tags":["loginflow"],"description":"Returns a short-lived token. Open `/authorize?…&flow_preview=<token>` (or the hosted login with the same parameter) and that login runs the DRAFT of this flow instead of what is published, for that browser only. Nothing is published.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"loginflow_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["token","expires_in"],"properties":{"token":{"type":"string"},"expires_in":{"type":"integer"}}}}}},"400":{"description":"`flow_not_found` — The login flow does not exist in this account.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["flow_not_found","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/loginflow":{"get":{"operationId":"loginflow/list","summary":"List Loginflows","tags":["loginflow"],"description":"List Loginflows","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/LoginFlow"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"loginflow/create","summary":"Create Loginflow","tags":["loginflow"],"description":"Create Loginflow","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","graph"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"layout":{"type":"object","description":"Node positions in the editor. Presentation only.","additionalProperties":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}},"additionalProperties":false}}},"additionalProperties":false}}}},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"LoginFlow","content":{"application/json":{"schema":{"type":"object","required":["id","name","trigger","graph","revision","history","account","createdAt"],"properties":{"id":{"type":"string","description":"LoginFlow ID"},"name":{"type":"string","maxLength":200},"trigger":{"type":"string","enum":["login"]},"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer","description":"Monotonic edit counter, bumped on every update. A publish freezes the graph under the revision it had; a login in flight pins the revision it started on."},"published":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"history":{"type":"array","items":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"description":"Previously published graphs, newest first, for rollback."},"layout":{"type":"object","description":"Node positions in the editor. Presentation only.","additionalProperties":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}},"additionalProperties":false}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"LoginFlowMetadata","default":{}},"createdAt":{"type":"string","description":"LoginFlow creation date"},"updatedAt":{"type":"string","description":"LoginFlow updated date"}},"description":"LoginFlow"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/loginflow/{loginflow_id}":{"get":{"operationId":"loginflow/get","summary":"Get Loginflow","tags":["loginflow"],"description":"Get Loginflow","parameters":[{"schema":{"type":"string"},"in":"path","name":"loginflow_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"LoginFlow","content":{"application/json":{"schema":{"type":"object","required":["id","name","trigger","graph","revision","history","account","createdAt"],"properties":{"id":{"type":"string","description":"LoginFlow ID"},"name":{"type":"string","maxLength":200},"trigger":{"type":"string","enum":["login"]},"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer","description":"Monotonic edit counter, bumped on every update. A publish freezes the graph under the revision it had; a login in flight pins the revision it started on."},"published":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"history":{"type":"array","items":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"description":"Previously published graphs, newest first, for rollback."},"layout":{"type":"object","description":"Node positions in the editor. Presentation only.","additionalProperties":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}},"additionalProperties":false}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"LoginFlowMetadata","default":{}},"createdAt":{"type":"string","description":"LoginFlow creation date"},"updatedAt":{"type":"string","description":"LoginFlow updated date"}},"description":"LoginFlow"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"loginflow/update","summary":"Update Loginflow","tags":["loginflow"],"description":"Update Loginflow","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"layout":{"type":"object","description":"Node positions in the editor. Presentation only.","additionalProperties":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}},"additionalProperties":false}}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"loginflow_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"LoginFlow","content":{"application/json":{"schema":{"type":"object","required":["id","name","trigger","graph","revision","history","account","createdAt"],"properties":{"id":{"type":"string","description":"LoginFlow ID"},"name":{"type":"string","maxLength":200},"trigger":{"type":"string","enum":["login"]},"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer","description":"Monotonic edit counter, bumped on every update. A publish freezes the graph under the revision it had; a login in flight pins the revision it started on."},"published":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"history":{"type":"array","items":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"description":"Previously published graphs, newest first, for rollback."},"layout":{"type":"object","description":"Node positions in the editor. Presentation only.","additionalProperties":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}},"additionalProperties":false}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"LoginFlowMetadata","default":{}},"createdAt":{"type":"string","description":"LoginFlow creation date"},"updatedAt":{"type":"string","description":"LoginFlow updated date"}},"description":"LoginFlow"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"loginflow/delete","summary":"Delete Loginflow","tags":["loginflow"],"description":"Delete Loginflow","parameters":[{"schema":{"type":"string"},"in":"path","name":"loginflow_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"LoginFlow","content":{"application/json":{"schema":{"type":"object","required":["id","name","trigger","graph","revision","history","account","createdAt"],"properties":{"id":{"type":"string","description":"LoginFlow ID"},"name":{"type":"string","maxLength":200},"trigger":{"type":"string","enum":["login"]},"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer","description":"Monotonic edit counter, bumped on every update. A publish freezes the graph under the revision it had; a login in flight pins the revision it started on."},"published":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"history":{"type":"array","items":{"type":"object","required":["graph","revision","published_at"],"properties":{"graph":{"type":"object","required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_:.-]+$"},"type":{"anyOf":[{"type":"string","enum":["start"]},{"type":"string","enum":["identify"]},{"type":"string","enum":["methods"]},{"type":"string","enum":["credential"]},{"type":"string","enum":["action"]},{"type":"string","enum":["mfa_gate"]},{"type":"string","enum":["mfa_challenge"]},{"type":"string","enum":["mfa_enroll"]},{"type":"string","enum":["passkey_offer"]},{"type":"string","enum":["condition"]},{"type":"string","enum":["deny"]},{"type":"string","enum":["end"]},{"type":"string","enum":["consent"]},{"type":"string","enum":["set_data"]}]},"config":{"type":"object","additionalProperties":{}}},"additionalProperties":false},"maxItems":100},"edges":{"type":"array","items":{"type":"object","required":["from","handle","to"],"properties":{"from":{"type":"string","minLength":1,"maxLength":64},"handle":{"type":"string","minLength":1,"maxLength":32},"to":{"type":"string","minLength":1,"maxLength":64}},"additionalProperties":false},"maxItems":300}},"additionalProperties":false,"description":"A login flow graph: typed nodes joined by named handles. See the validation rules on publish."},"revision":{"type":"integer"},"published_at":{"type":"string"}},"additionalProperties":false},"description":"Previously published graphs, newest first, for rollback."},"layout":{"type":"object","description":"Node positions in the editor. Presentation only.","additionalProperties":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}},"additionalProperties":false}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"LoginFlowMetadata","default":{}},"createdAt":{"type":"string","description":"LoginFlow creation date"},"updatedAt":{"type":"string","description":"LoginFlow updated date"}},"description":"LoginFlow"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/notificationsubscription":{"get":{"operationId":"notificationsubscription/list","summary":"List Notificationsubscriptions","tags":["notificationsubscription"],"description":"List Notificationsubscriptions","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"string"},"in":"query","name":"active","required":false,"description":"Exact match on `active`. Equivalent to `?query=active:<value>`."},{"schema":{"type":"string"},"in":"query","name":"channel","required":false,"description":"Exact match on `channel`. Equivalent to `?query=channel:<value>`."},{"schema":{"type":"string"},"in":"query","name":"events","required":false,"description":"Exact match on `events`. Equivalent to `?query=events:<value>`."},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/NotificationSubscription"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"notificationsubscription/create","summary":"Create Notificationsubscription","tags":["notificationsubscription"],"description":"Create Notificationsubscription","requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"type":"object","required":["events","channel","config"],"properties":{"events":{"type":"array","items":{"type":"string","description":"Events to subscribe to (e.g., user.created)"}},"channel":{"type":"string","enum":["email"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["recipients"],"properties":{"recipients":{"type":"array","items":{"type":"string","description":"List of email addresses receiving notifications","format":"email"}}},"additionalProperties":false},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"NotificationSubscriptionEmailCreate","additionalProperties":false},{"type":"object","required":["events","channel","config"],"properties":{"events":{"type":"array","items":{"type":"string"},"description":"Events to subscribe to. For `webhook` subscriptions it must be a subset of the published catalog; any other value is rejected with a 400:\n\n- `user.created`: A user was created (signup, import or Management API).\n- `user.updated`: A user was updated.\n- `user.deleted`: A user was deleted.\n- `auth.login`: A user logged in.\n- `credentials.exported`: Someone exported the tenant's password hashes (`GET /user/export` with hashes).\n- `team.created`: A team was created.\n- `team.updated`: A team was updated.\n- `team.deleted`: A team was deleted.\n- `team.member.added`: A user was added to a team.\n- `team.member.updated`: A team member's roles changed.\n- `team.member.removed`: A user was removed from a team (or left it).\n- `team.invite.created`: An invitation to join a team was created.\n- `team.invite.revoked`: A pending team invitation was revoked.\n- `team.invite.accepted`: A team invitation was accepted.\n- `organization.created`: An organization was created.\n- `organization.updated`: An organization was updated (name, settings or admins).\n- `organization.deleted`: An organization was deleted. Its teams are no longer in any organization; no `team.updated` is sent for them.\n\n`team.*` and `organization.*` payloads are notices, not state: they carry ids (`team_id`, `organization_id`, `user_id`, `roles`, `invite_id`...) and the receiver reads whatever else it needs. Use the delivery id (`x-faable-delivery`) to deduplicate: retries keep it."},"channel":{"type":"string","enum":["webhook"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Webhook URL. Must be `https`. Deliveries to private, loopback, link-local or metadata addresses are refused at delivery time.","format":"uri"},"secret":{"type":"string","minLength":1,"description":"Secret used to sign the webhook payload. Optional: when omitted one is generated and returned once, in the `secret` field of the response."}},"additionalProperties":false},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"NotificationSubscriptionWebhookCreate","additionalProperties":false}],"description":"NotificationSubscriptionCreate"}}},"description":"NotificationSubscriptionCreate"},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"NotificationSubscription","content":{"application/json":{"schema":{"anyOf":[{"type":"object","required":["id","events","channel","active","config","account","createdAt"],"properties":{"id":{"type":"string","description":"NotificationSubscriptionEmail ID"},"events":{"type":"array","items":{"type":"string","description":"Events to subscribe to (e.g., user.created)"}},"channel":{"type":"string","enum":["email"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["recipients"],"properties":{"recipients":{"type":"array","items":{"type":"string","description":"List of email addresses receiving notifications","format":"email"}}},"additionalProperties":false},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionEmailMetadata","default":{}},"createdAt":{"type":"string","description":"NotificationSubscriptionEmail creation date"},"updatedAt":{"type":"string","description":"NotificationSubscriptionEmail updated date"}},"description":"NotificationSubscriptionEmail"},{"type":"object","required":["id","events","channel","active","config","account","createdAt"],"properties":{"id":{"type":"string","description":"NotificationSubscriptionWebhook ID"},"events":{"type":"array","items":{"type":"string"},"description":"Events to subscribe to. For `webhook` subscriptions it must be a subset of the published catalog; any other value is rejected with a 400:\n\n- `user.created`: A user was created (signup, import or Management API).\n- `user.updated`: A user was updated.\n- `user.deleted`: A user was deleted.\n- `auth.login`: A user logged in.\n- `credentials.exported`: Someone exported the tenant's password hashes (`GET /user/export` with hashes).\n- `team.created`: A team was created.\n- `team.updated`: A team was updated.\n- `team.deleted`: A team was deleted.\n- `team.member.added`: A user was added to a team.\n- `team.member.updated`: A team member's roles changed.\n- `team.member.removed`: A user was removed from a team (or left it).\n- `team.invite.created`: An invitation to join a team was created.\n- `team.invite.revoked`: A pending team invitation was revoked.\n- `team.invite.accepted`: A team invitation was accepted.\n- `organization.created`: An organization was created.\n- `organization.updated`: An organization was updated (name, settings or admins).\n- `organization.deleted`: An organization was deleted. Its teams are no longer in any organization; no `team.updated` is sent for them.\n\n`team.*` and `organization.*` payloads are notices, not state: they carry ids (`team_id`, `organization_id`, `user_id`, `roles`, `invite_id`...) and the receiver reads whatever else it needs. Use the delivery id (`x-faable-delivery`) to deduplicate: retries keep it."},"channel":{"type":"string","enum":["webhook"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Webhook URL","format":"uri"},"previous_secret_expires_at":{"type":"string","format":"date-time","description":"Set after a `rotate-secret`: until this instant deliveries are signed with both the new and the previous secret."}}},"status":{"anyOf":[{"type":"string","enum":["healthy"]},{"type":"string","enum":["failing"]}],"description":"`failing` after several consecutive failed delivery attempts; the next successful delivery (or a successful `test`) sets it back to `healthy`. A failing subscription keeps receiving deliveries: it is never deactivated automatically."},"consecutive_failures":{"type":"number","description":"Failed delivery attempts since the last success."},"last_failure_at":{"type":"string","format":"date-time"},"last_success_at":{"type":"string","format":"date-time"},"secret":{"type":"string","description":"The signing secret. Returned ONLY in the response of create (and of `rotate-secret`); get and list never include it. Store it when you create the subscription."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionWebhookMetadata","default":{}},"createdAt":{"type":"string","description":"NotificationSubscriptionWebhook creation date"},"updatedAt":{"type":"string","description":"NotificationSubscriptionWebhook updated date"}},"description":"NotificationSubscriptionWebhook"}],"description":"NotificationSubscription"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/notificationsubscription/{notificationsubscription_id}":{"get":{"operationId":"notificationsubscription/get","summary":"Get Notificationsubscription","tags":["notificationsubscription"],"description":"Get Notificationsubscription","parameters":[{"schema":{"type":"string"},"in":"path","name":"notificationsubscription_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"NotificationSubscription","content":{"application/json":{"schema":{"anyOf":[{"type":"object","required":["id","events","channel","active","config","account","createdAt"],"properties":{"id":{"type":"string","description":"NotificationSubscriptionEmail ID"},"events":{"type":"array","items":{"type":"string","description":"Events to subscribe to (e.g., user.created)"}},"channel":{"type":"string","enum":["email"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["recipients"],"properties":{"recipients":{"type":"array","items":{"type":"string","description":"List of email addresses receiving notifications","format":"email"}}},"additionalProperties":false},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionEmailMetadata","default":{}},"createdAt":{"type":"string","description":"NotificationSubscriptionEmail creation date"},"updatedAt":{"type":"string","description":"NotificationSubscriptionEmail updated date"}},"description":"NotificationSubscriptionEmail"},{"type":"object","required":["id","events","channel","active","config","account","createdAt"],"properties":{"id":{"type":"string","description":"NotificationSubscriptionWebhook ID"},"events":{"type":"array","items":{"type":"string"},"description":"Events to subscribe to. For `webhook` subscriptions it must be a subset of the published catalog; any other value is rejected with a 400:\n\n- `user.created`: A user was created (signup, import or Management API).\n- `user.updated`: A user was updated.\n- `user.deleted`: A user was deleted.\n- `auth.login`: A user logged in.\n- `credentials.exported`: Someone exported the tenant's password hashes (`GET /user/export` with hashes).\n- `team.created`: A team was created.\n- `team.updated`: A team was updated.\n- `team.deleted`: A team was deleted.\n- `team.member.added`: A user was added to a team.\n- `team.member.updated`: A team member's roles changed.\n- `team.member.removed`: A user was removed from a team (or left it).\n- `team.invite.created`: An invitation to join a team was created.\n- `team.invite.revoked`: A pending team invitation was revoked.\n- `team.invite.accepted`: A team invitation was accepted.\n- `organization.created`: An organization was created.\n- `organization.updated`: An organization was updated (name, settings or admins).\n- `organization.deleted`: An organization was deleted. Its teams are no longer in any organization; no `team.updated` is sent for them.\n\n`team.*` and `organization.*` payloads are notices, not state: they carry ids (`team_id`, `organization_id`, `user_id`, `roles`, `invite_id`...) and the receiver reads whatever else it needs. Use the delivery id (`x-faable-delivery`) to deduplicate: retries keep it."},"channel":{"type":"string","enum":["webhook"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Webhook URL","format":"uri"},"previous_secret_expires_at":{"type":"string","format":"date-time","description":"Set after a `rotate-secret`: until this instant deliveries are signed with both the new and the previous secret."}}},"status":{"anyOf":[{"type":"string","enum":["healthy"]},{"type":"string","enum":["failing"]}],"description":"`failing` after several consecutive failed delivery attempts; the next successful delivery (or a successful `test`) sets it back to `healthy`. A failing subscription keeps receiving deliveries: it is never deactivated automatically."},"consecutive_failures":{"type":"number","description":"Failed delivery attempts since the last success."},"last_failure_at":{"type":"string","format":"date-time"},"last_success_at":{"type":"string","format":"date-time"},"secret":{"type":"string","description":"The signing secret. Returned ONLY in the response of create (and of `rotate-secret`); get and list never include it. Store it when you create the subscription."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionWebhookMetadata","default":{}},"createdAt":{"type":"string","description":"NotificationSubscriptionWebhook creation date"},"updatedAt":{"type":"string","description":"NotificationSubscriptionWebhook updated date"}},"description":"NotificationSubscriptionWebhook"}],"description":"NotificationSubscription"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"notificationsubscription/update","summary":"Update Notificationsubscription","tags":["notificationsubscription"],"description":"Update Notificationsubscription","requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"events":{"type":"array","items":{"type":"string","description":"Events to subscribe to (e.g., user.created)"}},"channel":{"type":"string","enum":["email"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","properties":{"recipients":{"type":"array","items":{"type":"string","description":"List of email addresses receiving notifications","format":"email"}}}},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Metadata update"}}},{"type":"object","properties":{"events":{"type":"array","items":{"type":"string"},"description":"Events to subscribe to. For `webhook` subscriptions it must be a subset of the published catalog; any other value is rejected with a 400:\n\n- `user.created`: A user was created (signup, import or Management API).\n- `user.updated`: A user was updated.\n- `user.deleted`: A user was deleted.\n- `auth.login`: A user logged in.\n- `credentials.exported`: Someone exported the tenant's password hashes (`GET /user/export` with hashes).\n- `team.created`: A team was created.\n- `team.updated`: A team was updated.\n- `team.deleted`: A team was deleted.\n- `team.member.added`: A user was added to a team.\n- `team.member.updated`: A team member's roles changed.\n- `team.member.removed`: A user was removed from a team (or left it).\n- `team.invite.created`: An invitation to join a team was created.\n- `team.invite.revoked`: A pending team invitation was revoked.\n- `team.invite.accepted`: A team invitation was accepted.\n- `organization.created`: An organization was created.\n- `organization.updated`: An organization was updated (name, settings or admins).\n- `organization.deleted`: An organization was deleted. Its teams are no longer in any organization; no `team.updated` is sent for them.\n\n`team.*` and `organization.*` payloads are notices, not state: they carry ids (`team_id`, `organization_id`, `user_id`, `roles`, `invite_id`...) and the receiver reads whatever else it needs. Use the delivery id (`x-faable-delivery`) to deduplicate: retries keep it."},"channel":{"type":"string","enum":["webhook"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","properties":{"url":{"type":"string","description":"Webhook URL. Must be `https`.","format":"uri"},"secret":{"type":"string","minLength":1,"description":"Replaces the signing secret immediately, with no overlap. Prefer `POST /notificationsubscription/{id}/rotate-secret`, which keeps the previous secret valid for 24 hours."}}},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Metadata update"}}}],"description":"NotificationSubscriptionUpdate"}}},"description":"NotificationSubscriptionUpdate"},"parameters":[{"schema":{"type":"string"},"in":"path","name":"notificationsubscription_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"NotificationSubscription","content":{"application/json":{"schema":{"anyOf":[{"type":"object","required":["id","events","channel","active","config","account","createdAt"],"properties":{"id":{"type":"string","description":"NotificationSubscriptionEmail ID"},"events":{"type":"array","items":{"type":"string","description":"Events to subscribe to (e.g., user.created)"}},"channel":{"type":"string","enum":["email"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["recipients"],"properties":{"recipients":{"type":"array","items":{"type":"string","description":"List of email addresses receiving notifications","format":"email"}}},"additionalProperties":false},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionEmailMetadata","default":{}},"createdAt":{"type":"string","description":"NotificationSubscriptionEmail creation date"},"updatedAt":{"type":"string","description":"NotificationSubscriptionEmail updated date"}},"description":"NotificationSubscriptionEmail"},{"type":"object","required":["id","events","channel","active","config","account","createdAt"],"properties":{"id":{"type":"string","description":"NotificationSubscriptionWebhook ID"},"events":{"type":"array","items":{"type":"string"},"description":"Events to subscribe to. For `webhook` subscriptions it must be a subset of the published catalog; any other value is rejected with a 400:\n\n- `user.created`: A user was created (signup, import or Management API).\n- `user.updated`: A user was updated.\n- `user.deleted`: A user was deleted.\n- `auth.login`: A user logged in.\n- `credentials.exported`: Someone exported the tenant's password hashes (`GET /user/export` with hashes).\n- `team.created`: A team was created.\n- `team.updated`: A team was updated.\n- `team.deleted`: A team was deleted.\n- `team.member.added`: A user was added to a team.\n- `team.member.updated`: A team member's roles changed.\n- `team.member.removed`: A user was removed from a team (or left it).\n- `team.invite.created`: An invitation to join a team was created.\n- `team.invite.revoked`: A pending team invitation was revoked.\n- `team.invite.accepted`: A team invitation was accepted.\n- `organization.created`: An organization was created.\n- `organization.updated`: An organization was updated (name, settings or admins).\n- `organization.deleted`: An organization was deleted. Its teams are no longer in any organization; no `team.updated` is sent for them.\n\n`team.*` and `organization.*` payloads are notices, not state: they carry ids (`team_id`, `organization_id`, `user_id`, `roles`, `invite_id`...) and the receiver reads whatever else it needs. Use the delivery id (`x-faable-delivery`) to deduplicate: retries keep it."},"channel":{"type":"string","enum":["webhook"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Webhook URL","format":"uri"},"previous_secret_expires_at":{"type":"string","format":"date-time","description":"Set after a `rotate-secret`: until this instant deliveries are signed with both the new and the previous secret."}}},"status":{"anyOf":[{"type":"string","enum":["healthy"]},{"type":"string","enum":["failing"]}],"description":"`failing` after several consecutive failed delivery attempts; the next successful delivery (or a successful `test`) sets it back to `healthy`. A failing subscription keeps receiving deliveries: it is never deactivated automatically."},"consecutive_failures":{"type":"number","description":"Failed delivery attempts since the last success."},"last_failure_at":{"type":"string","format":"date-time"},"last_success_at":{"type":"string","format":"date-time"},"secret":{"type":"string","description":"The signing secret. Returned ONLY in the response of create (and of `rotate-secret`); get and list never include it. Store it when you create the subscription."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionWebhookMetadata","default":{}},"createdAt":{"type":"string","description":"NotificationSubscriptionWebhook creation date"},"updatedAt":{"type":"string","description":"NotificationSubscriptionWebhook updated date"}},"description":"NotificationSubscriptionWebhook"}],"description":"NotificationSubscription"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"notificationsubscription/delete","summary":"Delete Notificationsubscription","tags":["notificationsubscription"],"description":"Delete Notificationsubscription","parameters":[{"schema":{"type":"string"},"in":"path","name":"notificationsubscription_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"NotificationSubscription","content":{"application/json":{"schema":{"anyOf":[{"type":"object","required":["id","events","channel","active","config","account","createdAt"],"properties":{"id":{"type":"string","description":"NotificationSubscriptionEmail ID"},"events":{"type":"array","items":{"type":"string","description":"Events to subscribe to (e.g., user.created)"}},"channel":{"type":"string","enum":["email"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["recipients"],"properties":{"recipients":{"type":"array","items":{"type":"string","description":"List of email addresses receiving notifications","format":"email"}}},"additionalProperties":false},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionEmailMetadata","default":{}},"createdAt":{"type":"string","description":"NotificationSubscriptionEmail creation date"},"updatedAt":{"type":"string","description":"NotificationSubscriptionEmail updated date"}},"description":"NotificationSubscriptionEmail"},{"type":"object","required":["id","events","channel","active","config","account","createdAt"],"properties":{"id":{"type":"string","description":"NotificationSubscriptionWebhook ID"},"events":{"type":"array","items":{"type":"string"},"description":"Events to subscribe to. For `webhook` subscriptions it must be a subset of the published catalog; any other value is rejected with a 400:\n\n- `user.created`: A user was created (signup, import or Management API).\n- `user.updated`: A user was updated.\n- `user.deleted`: A user was deleted.\n- `auth.login`: A user logged in.\n- `credentials.exported`: Someone exported the tenant's password hashes (`GET /user/export` with hashes).\n- `team.created`: A team was created.\n- `team.updated`: A team was updated.\n- `team.deleted`: A team was deleted.\n- `team.member.added`: A user was added to a team.\n- `team.member.updated`: A team member's roles changed.\n- `team.member.removed`: A user was removed from a team (or left it).\n- `team.invite.created`: An invitation to join a team was created.\n- `team.invite.revoked`: A pending team invitation was revoked.\n- `team.invite.accepted`: A team invitation was accepted.\n- `organization.created`: An organization was created.\n- `organization.updated`: An organization was updated (name, settings or admins).\n- `organization.deleted`: An organization was deleted. Its teams are no longer in any organization; no `team.updated` is sent for them.\n\n`team.*` and `organization.*` payloads are notices, not state: they carry ids (`team_id`, `organization_id`, `user_id`, `roles`, `invite_id`...) and the receiver reads whatever else it needs. Use the delivery id (`x-faable-delivery`) to deduplicate: retries keep it."},"channel":{"type":"string","enum":["webhook"]},"active":{"type":"boolean","description":"Whether the subscription is active"},"config":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Webhook URL","format":"uri"},"previous_secret_expires_at":{"type":"string","format":"date-time","description":"Set after a `rotate-secret`: until this instant deliveries are signed with both the new and the previous secret."}}},"status":{"anyOf":[{"type":"string","enum":["healthy"]},{"type":"string","enum":["failing"]}],"description":"`failing` after several consecutive failed delivery attempts; the next successful delivery (or a successful `test`) sets it back to `healthy`. A failing subscription keeps receiving deliveries: it is never deactivated automatically."},"consecutive_failures":{"type":"number","description":"Failed delivery attempts since the last success."},"last_failure_at":{"type":"string","format":"date-time"},"last_success_at":{"type":"string","format":"date-time"},"secret":{"type":"string","description":"The signing secret. Returned ONLY in the response of create (and of `rotate-secret`); get and list never include it. Store it when you create the subscription."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"NotificationSubscriptionWebhookMetadata","default":{}},"createdAt":{"type":"string","description":"NotificationSubscriptionWebhook creation date"},"updatedAt":{"type":"string","description":"NotificationSubscriptionWebhook updated date"}},"description":"NotificationSubscriptionWebhook"}],"description":"NotificationSubscription"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/notificationsubscription/{notificationsubscription_id}/test":{"post":{"operationId":"notificationsubscription/test","summary":"Send a test event to a webhook","tags":["notificationsubscription"],"description":"Sends a signed `ping` event to the subscription URL right now and returns the outcome. The request is signed exactly like a real delivery (`x-faable-signature`, `x-faable-timestamp`, `x-faable-delivery`), with `payload: { subscription_id }`. It is not retried and is recorded in the Log as `webhook.ping`. A successful test sets a `failing` subscription back to `healthy`. Works on inactive subscriptions too.","parameters":[{"schema":{"type":"string"},"in":"path","name":"notificationsubscription_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["ok","event_id","duration_ms"],"properties":{"ok":{"type":"boolean","description":"Whether the endpoint answered 2xx."},"event_id":{"type":"string","description":"Id of the `ping` event (also sent as `x-faable-delivery`)."},"status":{"type":"number","description":"HTTP status the endpoint answered."},"duration_ms":{"type":"number"},"error":{"type":"string","description":"Why it failed: `HTTP <status>`, a timeout, a network error, or a refused destination."}},"additionalProperties":false}}}},"400":{"description":"`not_a_webhook` — The operation only applies to `webhook` subscriptions.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_a_webhook","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/notificationsubscription/{notificationsubscription_id}/rotate-secret":{"post":{"operationId":"notificationsubscription/rotateSecret","summary":"Rotate a webhook's signing secret","tags":["notificationsubscription"],"description":"Replaces the signing secret with a new one (generated, or the one in the body) and keeps the previous one valid for 24 hours. During that overlap every delivery carries two signatures, `x-faable-signature: sha256=<new>,sha256=<previous>`, so receivers can switch to the new secret at their own pace: accept a delivery when any of the signatures matches. The new secret is returned only in this response. Rotating again during an overlap drops the oldest secret.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"secret":{"type":"string","minLength":16,"description":"The new secret. Omit it (send `{}`) to have one generated."}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"notificationsubscription_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["id","secret","previous_secret_expires_at"],"properties":{"id":{"type":"string"},"secret":{"type":"string","description":"The new signing secret. This is the only time it is returned."},"previous_secret_expires_at":{"type":"string","format":"date-time","description":"Until this instant deliveries carry two signatures, one per secret."}},"additionalProperties":false}}}},"400":{"description":"`not_a_webhook` — The operation only applies to `webhook` subscriptions.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_a_webhook","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/notificationsubscription/{notificationsubscription_id}/deliveries/{delivery_id}/redeliver":{"post":{"operationId":"notificationsubscription/redeliver","summary":"Redeliver a webhook delivery","tags":["notificationsubscription"],"description":"Queues a past delivery (succeeded, failed or still retrying) to be sent again within the next few seconds, with the same event id (`x-faable-delivery`) and a fresh retry schedule (1 min, 5 min, 30 min, 2 h, 12 h on timeouts, network errors, 5xx and 429). The delivery id is `data.delivery_id` in the `webhook.<event>` Log rows. Deliveries are kept for 30 days after they finish.","parameters":[{"schema":{"type":"string"},"in":"path","name":"notificationsubscription_id","required":true},{"schema":{"type":"string"},"in":"path","name":"delivery_id","required":true,"description":"The delivery id: `data.delivery_id` of the `webhook.<event>` rows of the Log."}],"security":[{"bearerAuth":[]}],"responses":{"202":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["delivery_id","status","next_attempt_at"],"properties":{"delivery_id":{"type":"string"},"status":{"type":"string","enum":["pending"]},"next_attempt_at":{"type":"string","format":"date-time"}},"additionalProperties":false}}}},"400":{"description":"`not_a_webhook` — The operation only applies to `webhook` subscriptions.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_a_webhook","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`delivery_not_found` — No webhook delivery with that id for this subscription.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","delivery_not_found","account_not_found"]}}}]}}}},"409":{"description":"`delivery_in_progress` — The delivery is being attempted right now; retry the redeliver in a minute.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["delivery_in_progress"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/log":{"get":{"operationId":"log/list","summary":"List Logs","tags":["log"],"description":"List Logs","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"string"},"in":"query","name":"type","required":false,"description":"Exact match on `type`. Equivalent to `?query=type:<value>`."},{"schema":{"type":"string"},"in":"query","name":"status","required":false,"description":"Exact match on `status`. Equivalent to `?query=status:<value>`."},{"schema":{"type":"string"},"in":"query","name":"origin","required":false,"description":"Exact match on `origin`. Equivalent to `?query=origin:<value>`."},{"schema":{"type":"string"},"in":"query","name":"action","required":false,"description":"Exact match on `action`. Equivalent to `?query=action:<value>`."},{"schema":{"type":"string"},"in":"query","name":"browser","required":false,"description":"Exact match on `browser`. Equivalent to `?query=browser:<value>`."},{"schema":{"type":"string"},"in":"query","name":"user","required":false,"description":"Exact match on `user`. Equivalent to `?query=user:<value>`."},{"schema":{"type":"string"},"in":"query","name":"client","required":false,"description":"Exact match on `client`. Equivalent to `?query=client:<value>`."},{"schema":{"type":"string"},"in":"query","name":"connection","required":false,"description":"Exact match on `connection`. Equivalent to `?query=connection:<value>`."},{"schema":{"type":"string"},"in":"query","name":"team","required":false,"description":"Exact match on `team`. Equivalent to `?query=team:<value>`."},{"schema":{"type":"string"},"in":"query","name":"identity","required":false,"description":"Exact match on `identity`. Equivalent to `?query=identity:<value>`."},{"schema":{"type":"string"},"in":"query","name":"ticket","required":false,"description":"Exact match on `ticket`. Equivalent to `?query=ticket:<value>`."},{"schema":{"type":"string"},"in":"query","name":"since","required":false,"description":"Exact match on `since`. Equivalent to `?query=since:<value>`."},{"schema":{"type":"string"},"in":"query","name":"until","required":false,"description":"Exact match on `until`. Equivalent to `?query=until:<value>`."},{"schema":{"type":"string"},"in":"query","name":"expires_before","required":false,"description":"Exact match on `expires_before`. Equivalent to `?query=expires_before:<value>`."},{"schema":{"type":"string"},"in":"query","name":"expires_after","required":false,"description":"Exact match on `expires_after`. Equivalent to `?query=expires_after:<value>`."},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"array","items":{"type":"string","enum":["user","client","connection","team","identity"],"description":"Allowed expand paths on `log`: `user`, `client`, `connection`, `team`, `identity`."},"maxItems":5},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `?expand=user`)."},{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"query","name":"q","required":false,"description":"Full-text search across: `message`."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Log"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/log/{log_id}":{"get":{"operationId":"log/get","summary":"Get Log","tags":["log"],"description":"Get Log","parameters":[{"schema":{"type":"array","items":{"type":"string","enum":["user","client","connection","team","identity"],"description":"Allowed expand paths on `log`: `user`, `client`, `connection`, `team`, `identity`."},"maxItems":5},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `?expand=user`)."},{"schema":{"type":"string"},"in":"path","name":"log_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Log","content":{"application/json":{"schema":{"type":"object","required":["id","type","status","account","createdAt"],"properties":{"id":{"type":"string","description":"Log ID"},"type":{"type":"string","description":"Log type, e.g. email.user.created"},"status":{"anyOf":[{"type":"string","enum":["success"]},{"type":"string","enum":["failed"]},{"type":"string","enum":["skipped"]},{"type":"string","enum":["info"]}],"description":"Log status"},"message":{"type":"string","description":"Optional log message"},"data":{"description":"Type-specific structured payload"},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"client":{"anyOf":[{"$ref":"#/components/schemas/Client"},{"type":"string"},{}]},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"team":{"anyOf":[{"$ref":"#/components/schemas/Team"},{"type":"string"},{}]},"identity":{"anyOf":[{"$ref":"#/components/schemas/Identity"},{"type":"string"},{}]},"ticket":{"type":"string"},"expires_at":{"type":"string","format":"date-time","description":"When this log row will be auto-deleted by the Mongo TTL monitor. Absent on rows recorded without `ttl_seconds`."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"LogMetadata","default":{}},"createdAt":{"type":"string","description":"Log creation date"},"updatedAt":{"type":"string","description":"Log updated date"}},"description":"Log"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/session":{"get":{"operationId":"session/list","summary":"List Sessions","tags":["session"],"description":"List Sessions","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"string"},"in":"query","name":"user","required":false,"description":"Exact match on `user`. Equivalent to `?query=user:<value>`."},{"schema":{"type":"string"},"in":"query","name":"client","required":false,"description":"Exact match on `client`. Equivalent to `?query=client:<value>`."},{"schema":{"type":"string"},"in":"query","name":"connection","required":false,"description":"Exact match on `connection`. Equivalent to `?query=connection:<value>`."},{"schema":{"type":"string"},"in":"query","name":"status","required":false,"description":"Exact match on `status`. Equivalent to `?query=status:<value>`."},{"schema":{"type":"string"},"in":"query","name":"sid","required":false,"description":"Exact match on `sid`. Equivalent to `?query=sid:<value>`."},{"schema":{"type":"string"},"in":"query","name":"since","required":false,"description":"Exact match on `since`. Equivalent to `?query=since:<value>`."},{"schema":{"type":"string"},"in":"query","name":"until","required":false,"description":"Exact match on `until`. Equivalent to `?query=until:<value>`."},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"array","items":{"type":"string","enum":["user","client","connection"],"description":"Allowed expand paths on `session`: `user`, `client`, `connection`."},"maxItems":3},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `?expand=user`)."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Session"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/session/{session_id}":{"get":{"operationId":"session/get","summary":"Get Session","tags":["session"],"description":"Get Session","parameters":[{"schema":{"type":"array","items":{"type":"string","enum":["user","client","connection"],"description":"Allowed expand paths on `session`: `user`, `client`, `connection`."},"maxItems":3},"in":"query","name":"expand","required":false,"description":"Expand id-only fields in the response. Pass one path per entry (e.g. `?expand=user`)."},{"schema":{"type":"string"},"in":"path","name":"session_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Session","content":{"application/json":{"schema":{"type":"object","required":["id","user","sid","status","last_seen_at","expires_at","account","createdAt"],"properties":{"id":{"type":"string","description":"Session ID"},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"sid":{"type":"string","description":"OIDC session id. Every id_token minted for this session carries it as the `sid` claim; a back-channel or front-channel logout names the session with it."},"status":{"anyOf":[{"type":"string","enum":["active"]},{"type":"string","enum":["revoked"]}],"description":"`active` or `revoked`. An active session whose `expires_at` is in the past has expired: nothing can refresh it any more, but the row stays for the history."},"ip":{"type":"string","description":"Client IP at the last authentication."},"user_agent":{"type":"string","description":"Raw `User-Agent` header at the last authentication."},"language":{"type":"string","description":"First language tag of the browser's `Accept-Language` at the last authentication, e.g. `es-ES`."},"device_name":{"type":"string","description":"The name a device gave itself when it asked for a code (device authorization grant), e.g. the hostname `faable login` runs on. Absent for browser sessions: a browser does not expose one."},"client":{"anyOf":[{"$ref":"#/components/schemas/Client"},{"type":"string"},{}]},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"issuer":{"type":"string","description":"Issuer URL the session was minted on."},"last_seen_at":{"type":"string","format":"date-time","description":"Last authentication or token refresh through this session. Each refresh slides `expires_at` 30 days from here."},"expires_at":{"type":"string","format":"date-time","description":"When the session stops being usable unless something refreshes it first."},"revoked_at":{"type":"string","format":"date-time"},"revoked_reason":{"anyOf":[{"type":"string","enum":["admin"]},{"type":"string","enum":["logout"]},{"type":"string","enum":["user"]},{"type":"string","enum":["password_change"]}],"description":"`admin`: revoked through the Management API. `logout`: the user signed out. `user`: the user revoked it from another device. `password_change`: the user set or changed their password, which ends every other session."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"SessionMetadata","default":{}},"createdAt":{"type":"string","description":"Session creation date"},"updatedAt":{"type":"string","description":"Session updated date"}},"description":"Session"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/session/{session_id}/revoke":{"post":{"operationId":"session/revoke","summary":"Revoke a session","tags":["session"],"description":"Ends the session for good: the browser cookie behind it stops working, every refresh token issued through it is refused on its next use (`invalid_grant`), and the relying parties that took part in it are notified. Access and id tokens already issued keep working until they expire. The row stays, marked `revoked`, for the device history.","parameters":[{"schema":{"type":"string"},"in":"path","name":"session_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Session","content":{"application/json":{"schema":{"type":"object","required":["id","user","sid","status","last_seen_at","expires_at","account","createdAt"],"properties":{"id":{"type":"string","description":"Session ID"},"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"string"},{}]},"sid":{"type":"string","description":"OIDC session id. Every id_token minted for this session carries it as the `sid` claim; a back-channel or front-channel logout names the session with it."},"status":{"anyOf":[{"type":"string","enum":["active"]},{"type":"string","enum":["revoked"]}],"description":"`active` or `revoked`. An active session whose `expires_at` is in the past has expired: nothing can refresh it any more, but the row stays for the history."},"ip":{"type":"string","description":"Client IP at the last authentication."},"user_agent":{"type":"string","description":"Raw `User-Agent` header at the last authentication."},"language":{"type":"string","description":"First language tag of the browser's `Accept-Language` at the last authentication, e.g. `es-ES`."},"device_name":{"type":"string","description":"The name a device gave itself when it asked for a code (device authorization grant), e.g. the hostname `faable login` runs on. Absent for browser sessions: a browser does not expose one."},"client":{"anyOf":[{"$ref":"#/components/schemas/Client"},{"type":"string"},{}]},"connection":{"anyOf":[{"$ref":"#/components/schemas/Connection"},{"type":"string"},{}]},"issuer":{"type":"string","description":"Issuer URL the session was minted on."},"last_seen_at":{"type":"string","format":"date-time","description":"Last authentication or token refresh through this session. Each refresh slides `expires_at` 30 days from here."},"expires_at":{"type":"string","format":"date-time","description":"When the session stops being usable unless something refreshes it first."},"revoked_at":{"type":"string","format":"date-time"},"revoked_reason":{"anyOf":[{"type":"string","enum":["admin"]},{"type":"string","enum":["logout"]},{"type":"string","enum":["user"]},{"type":"string","enum":["password_change"]}],"description":"`admin`: revoked through the Management API. `logout`: the user signed out. `user`: the user revoked it from another device. `password_change`: the user set or changed their password, which ends every other session."},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"SessionMetadata","default":{}},"createdAt":{"type":"string","description":"Session creation date"},"updatedAt":{"type":"string","description":"Session updated date"}},"description":"Session"}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"409":{"description":"`session_already_revoked` — The session was already revoked.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["session_already_revoked"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/customdomain/{customdomain_id}/retry":{"post":{"operationId":"customdomain/retry","summary":"Trigger a domain verification check now","tags":["customdomain"],"description":"Forces an immediate DNS re-check of a Custom Domain in any state. Rate-limited to one check per minute. A verified (ACTIVE) domain stays ACTIVE while it is re-checked; any other state moves to VERIFYING.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"customdomain_id","required":true}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/customdomain":{"get":{"operationId":"customdomain/list","summary":"List Customdomains","tags":["customdomain"],"description":"List Customdomains","parameters":[{"schema":{"type":"number","minimum":1,"maximum":200},"in":"query","name":"pageSize","required":false,"description":"Number of items per page (max 200)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Cursor for next page"},{"schema":{"type":"string"},"in":"query","name":"next","required":false,"description":"Cursor returned by the previous page"},{"schema":{"type":"boolean"},"in":"query","name":"count","required":false,"description":"Also return `total`: the rows matching the whole filter, across every page."},{"schema":{"type":"string"},"in":"query","name":"query","required":false,"description":"Filter using a FaableQL query"},{"schema":{"type":"string","minLength":1,"maxLength":200},"in":"query","name":"q","required":false,"description":"Full-text search across: `domain`."}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["next","results"],"properties":{"next":{"anyOf":[{"type":"string"},{"type":"null"}]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Customdomain"}},"total":{"type":"integer","description":"Rows matching the whole filter, across every page. Only with `?count=true`."}},"additionalProperties":false}}}},"400":{"description":"`invalid_query` — The `?query=` FaableQL expression could not be parsed.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`search_not_supported` — This resource does not support `?search=`.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_query","invalid_expand","search_not_supported","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"customdomain/create","summary":"Create Customdomain","tags":["customdomain"],"description":"Create Customdomain","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"Add Metadata"}},"description":"CustomdomainCreate","additionalProperties":false}}},"description":"CustomdomainCreate"},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Customdomain","content":{"application/json":{"schema":{"type":"object","required":["id","domain","token","status","attempts","account","createdAt"],"properties":{"id":{"type":"string","description":"Customdomain ID"},"domain":{"type":"string"},"token":{"type":"string"},"status":{"enum":["PENDING","VERIFYING","ACTIVE","FAILED","DEGRADED"]},"attempts":{"type":"number"},"nextCheck":{"type":"string","format":"date-time"},"lastCheck":{"type":"string","format":"date-time"},"errorLog":{"type":"string","description":"User feedback error from last check"},"verifiedAt":{"type":"string","format":"date-time"},"records":{"type":"array","items":{"type":"object","required":["type","host","expected","observed","ok"],"properties":{"type":{"type":"string"},"host":{"type":"string"},"expected":{"type":"string"},"observed":{"type":"array","items":{"type":"string"}},"ok":{"type":"boolean"}}},"description":"Per-record DNS status from the last check: what each record points to now vs. expected"},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"CustomdomainMetadata","default":{}},"createdAt":{"type":"string","description":"Customdomain creation date"},"updatedAt":{"type":"string","description":"Customdomain updated date"}},"description":"Customdomain"}}}},"400":{"description":"`already_exists` — A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["already_exists","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/customdomain/{customdomain_id}":{"get":{"operationId":"customdomain/get","summary":"Get Customdomain","tags":["customdomain"],"description":"Get Customdomain","parameters":[{"schema":{"type":"string"},"in":"path","name":"customdomain_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Customdomain","content":{"application/json":{"schema":{"type":"object","required":["id","domain","token","status","attempts","account","createdAt"],"properties":{"id":{"type":"string","description":"Customdomain ID"},"domain":{"type":"string"},"token":{"type":"string"},"status":{"enum":["PENDING","VERIFYING","ACTIVE","FAILED","DEGRADED"]},"attempts":{"type":"number"},"nextCheck":{"type":"string","format":"date-time"},"lastCheck":{"type":"string","format":"date-time"},"errorLog":{"type":"string","description":"User feedback error from last check"},"verifiedAt":{"type":"string","format":"date-time"},"records":{"type":"array","items":{"type":"object","required":["type","host","expected","observed","ok"],"properties":{"type":{"type":"string"},"host":{"type":"string"},"expected":{"type":"string"},"observed":{"type":"array","items":{"type":"string"}},"ok":{"type":"boolean"}}},"description":"Per-record DNS status from the last check: what each record points to now vs. expected"},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"CustomdomainMetadata","default":{}},"createdAt":{"type":"string","description":"Customdomain creation date"},"updatedAt":{"type":"string","description":"Customdomain updated date"}},"description":"Customdomain"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`invalid_expand` — An `?expand=` path is not allowed on this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","invalid_expand","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"customdomain/delete","summary":"Delete Customdomain","tags":["customdomain"],"description":"Delete Customdomain","parameters":[{"schema":{"type":"string"},"in":"path","name":"customdomain_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Customdomain","content":{"application/json":{"schema":{"type":"object","required":["id","domain","token","status","attempts","account","createdAt"],"properties":{"id":{"type":"string","description":"Customdomain ID"},"domain":{"type":"string"},"token":{"type":"string"},"status":{"enum":["PENDING","VERIFYING","ACTIVE","FAILED","DEGRADED"]},"attempts":{"type":"number"},"nextCheck":{"type":"string","format":"date-time"},"lastCheck":{"type":"string","format":"date-time"},"errorLog":{"type":"string","description":"User feedback error from last check"},"verifiedAt":{"type":"string","format":"date-time"},"records":{"type":"array","items":{"type":"object","required":["type","host","expected","observed","ok"],"properties":{"type":{"type":"string"},"host":{"type":"string"},"expected":{"type":"string"},"observed":{"type":"array","items":{"type":"string"}},"ok":{"type":"boolean"}}},"description":"Per-record DNS status from the last check: what each record points to now vs. expected"},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"CustomdomainMetadata","default":{}},"createdAt":{"type":"string","description":"Customdomain creation date"},"updatedAt":{"type":"string","description":"Customdomain updated date"}},"description":"Customdomain"}}}},"400":{"description":"`invalid_id` — The id in the path is not a valid id for this resource.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_id","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/sdk-health/{account_id}":{"get":{"operationId":"account/sdk-health","summary":"Library health for this account","tags":["account"],"description":"Which Faable client libraries this account is being called with, at which versions, and how far behind the latest published release each one is. Versions are observed from the `x-faable-client` header, so an integration that talks raw OIDC shows up only in the traffic breakdown.","parameters":[{"schema":{"type":"string"},"in":"path","name":"account_id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["libraries","traffic","sign_in_flows","latest_checked_at","retention_days"],"properties":{"libraries":{"type":"array","items":{"type":"object","required":["name","package","latest","installs"],"properties":{"name":{"type":"string"},"package":{"type":"string"},"latest":{"anyOf":[{"type":"string"},{"type":"null"}]},"installs":{"type":"array","items":{"type":"object","required":["version","sha","app","lag","legacy_unversioned","first_seen","last_seen"],"properties":{"version":{"type":"string"},"sha":{"anyOf":[{"type":"string"},{"type":"null"}]},"app":{"anyOf":[{"type":"object","required":["id","client_id","name"],"properties":{"id":{"type":"string"},"client_id":{"type":"string"},"name":{"type":"string"}}},{"type":"null"}]},"lag":{"anyOf":[{"type":"string","enum":["current"]},{"type":"string","enum":["patch"]},{"type":"string","enum":["minor"]},{"type":"string","enum":["major"]},{"type":"string","enum":["unknown"]}]},"legacy_unversioned":{"type":"boolean"},"first_seen":{"type":"string"},"last_seen":{"type":"string"}}}}}}},"traffic":{"anyOf":[{"type":"object","required":["days","total","by_class"],"properties":{"days":{"type":"number"},"total":{"type":"number"},"by_class":{"type":"object","additionalProperties":{"type":"number"}}}},{"type":"null"}]},"sign_in_flows":{"anyOf":[{"type":"object","required":["days","apps"],"properties":{"days":{"type":"number"},"apps":{"type":"array","items":{"type":"object","required":["app","pkce","no_pkce","implicit","password","last_legacy_day"],"properties":{"app":{"type":"object","required":["id","client_id","name"],"properties":{"id":{"type":"string"},"client_id":{"type":"string"},"name":{"type":"string"}}},"pkce":{"type":"number"},"no_pkce":{"type":"number"},"implicit":{"type":"number"},"password":{"type":"number"},"last_legacy_day":{"anyOf":[{"type":"string"},{"type":"null"}]}}}}}},{"type":"null"}]},"latest_checked_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"retention_days":{"type":"number"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/sdk/report":{"post":{"operationId":"sdk/report","summary":"Report a client-side sign-in failure","tags":["sdk"],"description":"Called by the browser SDKs when a sign-in redirect could not be completed on the client (the code exchange stalled or had no PKCE verifier). A missing verifier adds an entry to the tenant log, at most once an hour per application; the response is always empty.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["client_id","event"],"properties":{"client_id":{"type":"string","minLength":1,"maxLength":128},"event":{"anyOf":[{"type":"string","enum":["callback_stalled"]},{"type":"string","enum":["callback_no_verifier"]},{"type":"string","enum":["callback_recovered"]}]},"stage":{"type":"string","maxLength":64,"description":"Last step the SDK reached before reporting (`detected`, `verifier_loaded`, `token_request_sent`, `token_response`…)."},"elapsed_ms":{"type":"integer","minimum":0,"maximum":3600000},"code_prefix":{"type":"string","pattern":"^[0-9a-f-]{1,8}$","description":"First characters of the authorization code, to join the report with the `Generated code=` and `/oauth/token` lines of the same sign-in."},"detail":{"type":"string","maxLength":500}},"additionalProperties":false}}}},"responses":{"204":{"description":"Default Response"},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/connect/handoff":{"post":{"operationId":"connect/createHandoff","summary":"Create a one-time handoff of a client’s credentials","tags":["connect"],"description":"Used by the dashboard in the «Connect with Faable» flow. Checks that `client_id` belongs to `account_id` and that its callbacks contain `<site_url>/wp-login.php?faable-auth=callback`, then stores a one-time handoff bound to the PKCE `code_challenge`, valid for 10 minutes. The response `code` is sent to the site, which redeems it with `POST /connect/exchange`. The client secret is never part of this request or response. Requires a management token for `account_id` with `update:clients`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["account_id","client_id","code_challenge","code_challenge_method","site_url"],"properties":{"account_id":{"type":"string","pattern":"^account_[0-9a-f]{24}$","description":"The tenant that owns the client."},"client_id":{"type":"string","minLength":1,"maxLength":200,"description":"The OAuth `client_id` of the client created for the site (the resource `id`, `client_…`, is accepted too)."},"code_challenge":{"type":"string","pattern":"^[A-Za-z0-9_-]{43}$","description":"The site's PKCE challenge: BASE64URL(SHA256(code_verifier)), unpadded."},"code_challenge_method":{"type":"string","enum":["S256"]},"site_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The site's base URL. The client's callbacks must contain `<site_url>/wp-login.php?faable-auth=callback`."}},"additionalProperties":false}}}},"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["code","expires_at"],"properties":{"code":{"type":"string","description":"One-time code for `POST /connect/exchange`. Shown once; only its hash is stored."},"expires_at":{"type":"string","format":"date-time"}}}}}},"400":{"description":"`account_mismatch` — The `account_id` in the body is not the account the request is scoped to (`x-faableauth-account`).\n\n`connect_redirect_uri_missing` — The client's callbacks do not include `<site_url>/wp-login.php?faable-auth=callback`.\n\n`invalid_site_url` — `site_url` is not an absolute http(s) URL without query string or fragment.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_mismatch","connect_redirect_uri_missing","invalid_site_url","validation_error"]}}}]}}}},"401":{"description":"`unauthorized` — The request carries no valid credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["unauthorized"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","insufficient_scope","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).\n\n`client_not_found` — No client with that `client_id`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found","client_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/connect/exchange":{"post":{"operationId":"connect/exchange","summary":"Redeem a connect handoff for the client’s credentials","tags":["connect"],"description":"Server-to-server, unauthenticated: the PKCE `code_verifier` is the proof. Returns the credentials of the client the handoff names, once — a second redemption answers 410 `handoff_used`. Errors carry `error` (same value as `error_code`). Responses are `Cache-Control: no-store`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code","code_verifier"],"properties":{"code":{"type":"string","minLength":1,"maxLength":200},"code_verifier":{"type":"string","pattern":"^[A-Za-z0-9._~-]{43,128}$","description":"The PKCE verifier behind the handoff `code_challenge`."}},"additionalProperties":false}}}},"security":[],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["auth_domain","client_id","client_secret","project_id","account_id"],"properties":{"auth_domain":{"type":"string","description":"Base URL of the tenant: `<auth_domain>/.well-known/openid-configuration` is its discovery document."},"client_id":{"type":"string"},"client_secret":{"type":"string"},"project_id":{"anyOf":[{"type":"string"},{"type":"null"}]},"account_id":{"type":"string"}}}}}},"400":{"description":"`invalid_code_verifier` — `code_verifier` does not match the `code_challenge` of the handoff.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_code_verifier","validation_error"]}}}]}}}},"404":{"description":"`handoff_not_found` — No connect handoff matches that code.\n\n`client_not_found` — No client with that `client_id`.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["handoff_not_found","client_not_found","account_not_found"]}}}]}}}},"410":{"description":"`handoff_expired` — The connect handoff is past its expiry. Start again.\n\n`handoff_used` — The connect handoff was already redeemed.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["handoff_expired","handoff_used"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/passwordless/verify_redirect":{"get":{"operationId":"passwordless/verify_redirect","summary":"Receive a passwordless token via email link and convert it to a valid session","tags":["passwordless"],"description":"Receive a passwordless token and convert it to a valid session","parameters":[{"schema":{"type":"string"},"in":"query","name":"token","required":true}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`expired_link` — The magic link token was not found or has expired.\n\n`invalid_link` — The magic link is malformed.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["expired_link","invalid_link","validation_error"]}}}]}}}},"401":{"description":"`login_denied` — A post-login Action denied the sign-in. `message` carries the reason it gave. On token grants it is a 403 with `error: invalid_grant`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["login_denied"]}}}]}}}},"403":{"description":"`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/passwordless/verify_code":{"post":{"operationId":"passwordless/verifyCode","summary":"Verify a passwordless code and establish a session","tags":["passwordless"],"description":"Verifies an emailed passwordless code and establishes the browser `faable_sess` session, returning where to send the user next. Called by the first-party hosted login UI; SPAs on other origins use the `passwordless/otp` grant at `/oauth/token` instead, which returns tokens and no cookie.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["client_id","email","otp"],"properties":{"client_id":{"type":"string"},"email":{"type":"string"},"otp":{"type":"string"},"redirect_to":{"type":"string","description":"Same-origin path to return the user to after authentication, instead of the OAuth client redirect. Used by first-party interactive flows (e.g. device-code confirm). Non same-origin values are ignored."},"remember_me":{"type":"boolean","description":"The \"Remember me on this device\" checkbox. Honoured only when the tenant shows one (`login_methods.remember_me: \"optional\"`)."},"state":{"type":"string","description":"The login's key from `/authorize` — the hosted screen's `?state=`. Carries the client's OAuth request (its `redirect_uri`, its own `state`, PKCE challenge, nonce, scope) into the code this login issues. Absent when the screen was not opened by `/authorize` (device-code confirm, account pages)."}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["redirect_url"],"properties":{"redirect_url":{"type":"string"}}}}}},"400":{"description":"`invalid_client` — The `client_id` in the request does not name a client of this account.\n\n`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_client","invalid_state","validation_error"]}}}]}}}},"401":{"description":"`invalid_otp` — The one-time password is wrong or expired.\n\n`login_denied` — A post-login Action denied the sign-in. `message` carries the reason it gave. On token grants it is a 403 with `error: invalid_grant`.\n\n`state_mismatch` — The `state` belongs to another account, session, client or ceremony.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_otp","login_denied","state_mismatch"]}}}]}}}},"403":{"description":"`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/passwordless/start":{"post":{"operationId":"passwordless/start","summary":"Start Passwordless authentication flow","tags":["passwordless"],"description":"Start Passwordless authentication flow","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["client_id","email","send"],"properties":{"client_id":{"type":"string"},"email":{"type":"string"},"send":{"type":"string","default":"link"},"connection_id":{"type":"string"},"auth_params":{"type":"object","additionalProperties":{"type":"string"}},"resume_url":{"type":"string","description":"Page to go back to from the code email: it adds a button that opens it. Only kept when its origin is one of the client's `callbacks` or `web_origins`; otherwise ignored, never an error."},"state":{"type":"string","description":"The login's key from `/authorize` — the hosted screen's `?state=`. Only ties the code this start issues to that login for tracing; it is not consumed and never rejects the request (`/passwordless/verify_code` is the step that uses it)."}}}}}},"responses":{"200":{"description":"Default Response"},"400":{"description":"`ambiguous_connection` — Several connections match; pass `connection_id`.\n\n`invalid_client` — The `client_id` in the request does not name a client of this account.\n\n`invalid_connection` — The connection does not exist, is disabled, or is not of the type this flow needs.\n\n`invalid_email` — The email address is not valid.\n\n`passwordless_unavailable` — No passwordless connection is configured or enabled for this client.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["ambiguous_connection","invalid_client","invalid_connection","invalid_email","passwordless_unavailable","validation_error"]}}}]}}}},"403":{"description":"`origin_not_allowed` — The request `Origin` is not in the client's Allowed Web Origins.\n\n`signup_denied` — A pre-signup Action or an active block denied the sign-up. `message` carries the reason.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["origin_not_allowed","signup_denied"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/mfa/challenge/options":{"get":{"summary":"What the second-factor screen should offer","tags":["mfa"],"description":"For a login parked on the hosted challenge screen: which factors the user can answer with, in the order and with the default the login flow chose, and whether recovery codes are an option. Reads the state without consuming it.","parameters":[{"schema":{"type":"string"},"in":"query","name":"state","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["factors","recovery"],"properties":{"factors":{"type":"array","items":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}]},"description":"The ways this user can answer, in the order the screen should offer them. Only factors the user actually has and the policy accepts."},"preferred":{"anyOf":[{"type":"string","enum":["totp"]},{"type":"string","enum":["webauthn"]}],"description":"The one to mark as the default, when there are several."},"recovery":{"type":"boolean","description":"Whether to offer the recovery-code way in: the flow allows it and the user has codes."}},"additionalProperties":false}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`mfa_invalid_code` — The authenticator code did not verify.\n\n`mfa_invalid_recovery_code` — The recovery code did not verify.\n\n`totp_not_allowed` — This account does not accept authenticator apps.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["mfa_invalid_code","mfa_invalid_recovery_code","totp_not_allowed"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/mfa/verify":{"post":{"summary":"Answer an MFA challenge with a TOTP code","tags":["mfa"],"description":"Verifies a time-based code against the factors enrolled by the user the challenge belongs to, and resumes the parked login. The state is consumed only on success, so a mistyped code does not destroy the flow.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state","code"],"properties":{"state":{"type":"string"},"code":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["redirect_url"],"properties":{"redirect_url":{"type":"string"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/mfa/recovery":{"post":{"summary":"Answer an MFA challenge with a recovery code","tags":["mfa"],"description":"Consumes one of the one-time recovery codes issued when the user enrolled their first factor.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state","code"],"properties":{"state":{"type":"string"},"code":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["redirect_url"],"properties":{"redirect_url":{"type":"string"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me/factors":{"get":{"summary":"List my second factors","tags":["mfa"],"description":"The security methods enrolled by the current session's user. Never returns secret material: TOTP seeds and recovery-code hashes are redacted by the store.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FactorSummary"}}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me/factors/totp":{"post":{"summary":"Start enrolling an authenticator app","tags":["mfa"],"description":"Creates an UNCONFIRMED TOTP factor and returns its `otpauth://` URI and secret. When the user already has one that was started and never confirmed, that one is returned instead of a new one, so an interrupted setup continues with the seed already scanned. Once confirmed, the secret is never readable again. The factor does not satisfy any policy until `POST /me/factors/:id/verify` proves the user can produce a code from it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":100},"state":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["id","secret","otpauth_uri"],"properties":{"id":{"type":"string"},"secret":{"type":"string"},"otpauth_uri":{"type":"string"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me/factors/{factor_id}/verify":{"post":{"summary":"Confirm an enrolment","tags":["mfa"],"description":"Proves the user can produce a code from the seed, which is what makes the factor count. Confirming the first factor also mints the recovery codes — returned once and never again.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string"},"state":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"factor_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["id","confirmed_at"],"properties":{"id":{"type":"string"},"confirmed_at":{"type":"string"},"recovery_codes":{"type":"array","items":{"type":"string"}},"redirect_url":{"type":"string"}}}}}},"400":{"description":"`factor_already_confirmed` — The factor was already confirmed.\n\n`factor_not_found` — No factor with that id belongs to this user.\n\n`invalid_code` — The verification code is wrong, expired or already used.\n\n`invalid_factor` — The factor cannot be verified (no secret material).\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["factor_already_confirmed","factor_not_found","invalid_code","invalid_factor","validation_error"]}}}]}}}},"401":{"description":"`state_mismatch` — The `state` belongs to another account, session, client or ceremony.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["state_mismatch"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me/factors/{factor_id}":{"delete":{"summary":"Remove one of my second factors","tags":["mfa"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"factor_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"boolean"}}}}}},"400":{"description":"`factor_not_found` — No factor with that id belongs to this user.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["factor_not_found","validation_error"]}}}]}}}},"401":{"description":"`mfa_pending` — A second-factor challenge is pending; answer it before continuing.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["mfa_pending"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me/passkeys/options":{"post":{"summary":"Start registering a passkey","tags":["mfa"],"description":"Returns the WebAuthn creation options for `navigator.credentials.create()`. Existing credentials are excluded so the authenticator refuses to enrol the same key twice. Pass the `state` of a forced enrolment so the guard knows this is the way through, not a bypass.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"state":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["ceremony_id","options"],"properties":{"ceremony_id":{"type":"string"},"options":{}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me/passkeys":{"post":{"summary":"Finish registering a passkey","tags":["mfa"],"description":"Verifies the attestation and stores the credential as a confirmed second factor. Pass the `state` of a pending challenge to also resume the login it parked.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ceremony_id","credential"],"properties":{"ceremony_id":{"type":"string"},"credential":{},"name":{"type":"string","maxLength":100},"state":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string"},"redirect_url":{"type":"string"}}}}}},"400":{"description":"`passkey_verification_failed` — The WebAuthn response could not be verified.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["passkey_verification_failed","validation_error"]}}}]}}}},"401":{"description":"`state_mismatch` — The `state` belongs to another account, session, client or ceremony.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["state_mismatch"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/mfa/webauthn/options":{"post":{"summary":"Start answering a challenge with a passkey","tags":["mfa"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state"],"properties":{"state":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["ceremony_id","options"],"properties":{"ceremony_id":{"type":"string"},"options":{}}}}}},"400":{"description":"`no_passkey_enrolled` — The user has no passkey to sign in with.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["no_passkey_enrolled","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/mfa/webauthn/verify":{"post":{"summary":"Answer a challenge with a passkey","tags":["mfa"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ceremony_id","credential"],"properties":{"ceremony_id":{"type":"string"},"credential":{},"name":{"type":"string","maxLength":100},"state":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["redirect_url"],"properties":{"redirect_url":{"type":"string"}}}}}},"400":{"description":"`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_state","validation_error"]}}}]}}}},"401":{"description":"`state_mismatch` — The `state` belongs to another account, session, client or ceremony.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["state_mismatch"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/passkey/offer/skip":{"post":{"summary":"Decline the post-login passkey offer","tags":["mfa"],"description":"Finishes a login the passkey offer parked without registering anything. `dismissed` is the user saying \"not now\" (starts the snooze); `unsupported` is the screen finding the browser cannot do WebAuthn (the prompt is given back); `failed` is a registration that errored. Returns where the login was going.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state","reason"],"properties":{"state":{"type":"string"},"reason":{"anyOf":[{"type":"string","enum":["dismissed"]},{"type":"string","enum":["unsupported"]},{"type":"string","enum":["failed"]}]}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["redirect_url"],"properties":{"redirect_url":{"type":"string"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`state_mismatch` — The `state` belongs to another account, session, client or ceremony.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["state_mismatch"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/passkey/login/options":{"post":{"summary":"Start a passwordless passkey login","tags":["mfa"],"description":"Returns authentication options with an EMPTY allowCredentials, so the browser offers whichever discoverable passkey it holds for this Relying Party — the usernameless flow behind conditional autofill.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["client_id"],"properties":{"client_id":{"type":"string"},"redirect_to":{"type":"string"},"state":{"type":"string","description":"The login's key from `/authorize`. Only ties the ceremony to that login for tracing; not consumed, never rejects (`/passkey/login/verify` is the step that uses it)."}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["ceremony_id","options"],"properties":{"ceremony_id":{"type":"string"},"options":{}}}}}},"400":{"description":"`passkey_login_disabled` — Sign in with a passkey is not enabled for this client.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["passkey_login_disabled","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/passkey/login/verify":{"post":{"summary":"Finish a passwordless passkey login","tags":["mfa"],"description":"Verifies the assertion, establishes the session and answers with where to send the browser. A passkey that verified the user (biometric or PIN) is already two factors, so no second-factor challenge follows.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ceremony_id","credential","client_id"],"properties":{"ceremony_id":{"type":"string"},"credential":{},"client_id":{"type":"string"},"redirect_to":{"type":"string"},"state":{"type":"string"},"remember_me":{"type":"boolean","description":"The \"Remember me on this device\" checkbox. Honoured only when the tenant shows one (`login_methods.remember_me: \"optional\"`)."}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["redirect_url"],"properties":{"redirect_url":{"type":"string"}}}}}},"400":{"description":"`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`malformed_credential` — The WebAuthn credential is malformed.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_state","malformed_credential","validation_error"]}}}]}}}},"401":{"description":"`invalid_passkey` — The passkey assertion did not verify.\n\n`state_mismatch` — The `state` belongs to another account, session, client or ceremony.\n\n`unknown_passkey` — The passkey is not enrolled for this user.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_passkey","state_mismatch","unknown_passkey"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/mfa/step-up":{"post":{"summary":"Start a step-up challenge for the current session","tags":["mfa"],"description":"Parks a challenge for the signed-in user and returns where to send the browser. Answering it seals the session at AAL2 and returns to `redirect_to`. Answers `already_satisfied` when the session is AAL2 already.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"redirect_to":{"type":"string","description":"Same-origin path to return to once the factor is proven. Off-origin values are dropped."}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["redirect_url","already_satisfied"],"properties":{"redirect_url":{"type":"string"},"already_satisfied":{"type":"boolean"}}}}}},"400":{"description":"`no_usable_factor` — The user has no confirmed second factor to challenge.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["no_usable_factor","validation_error"]}}}]}}}},"401":{"description":"`not_logged_in` — There is no signed-in session for this request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_logged_in"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/mfa/recovery/options":{"get":{"summary":"What a recovery-by-factor screen should offer","tags":["mfa"],"parameters":[{"schema":{"type":"string"},"in":"query","name":"state","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["factors","recovery"],"properties":{"factors":{"type":"array","items":{"type":"string","enum":["totp"]},"description":"Factors the person can answer with here. Passkeys are not offered for recovery yet: their assertion flow is bound to the login session."},"preferred":{"type":"string","enum":["totp"]},"recovery":{"type":"boolean","description":"Whether they hold recovery codes."}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/mfa/recovery/verify":{"post":{"summary":"Answer a recovery-by-factor challenge with a TOTP code","tags":["mfa"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state","code"],"properties":{"state":{"type":"string"},"code":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["redirect_url"],"properties":{"redirect_url":{"type":"string"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`mfa_invalid_code` — The authenticator code did not verify.\n\n`mfa_invalid_recovery_code` — The recovery code did not verify.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["mfa_invalid_code","mfa_invalid_recovery_code"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/mfa/recovery/code":{"post":{"summary":"Answer a recovery-by-factor challenge with a recovery code","tags":["mfa"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state","code"],"properties":{"state":{"type":"string"},"code":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["redirect_url"],"properties":{"redirect_url":{"type":"string"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`mfa_invalid_code` — The authenticator code did not verify.\n\n`mfa_invalid_recovery_code` — The recovery code did not verify.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["mfa_invalid_code","mfa_invalid_recovery_code"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me/grants":{"get":{"summary":"Apps the signed-in user has connected","tags":["account"],"description":"The live grants of the session user: each app they allowed on the consent screen, what it can do and what they picked (e.g. the project).","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","client","resource","scopes","created_at"],"properties":{"id":{"type":"string"},"client":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"logo_uri":{"type":"string"},"client_uri":{"type":"string"},"metadata_document_url":{"type":"string"}}},"resource":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"options":{"type":"object","additionalProperties":{"type":"string"}},"created_at":{"type":"string"},"last_used_at":{"type":"string"}}}}}}}}},"401":{"description":"`not_logged_in` — There is no signed-in session for this request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_logged_in"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me/grants/{grant_id}":{"delete":{"summary":"Disconnect an app","tags":["account"],"description":"Revokes one of the session user’s grants. The app loses access at its next token refresh (within an hour), and has to ask again to reconnect.","parameters":[{"schema":{"type":"string"},"in":"path","name":"grant_id","required":true}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`not_logged_in` — There is no signed-in session for this request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_logged_in"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me":{"get":{"operationId":"session/me","summary":"Get current User","tags":["session"],"description":"Returns the User behind the current session or bearer token. Requires a valid session cookie or `Authorization: Bearer <access_token>` header. With a Bearer, the custom claims of that access token (connection claims_mapping, actions setCustomClaim) are included as top-level properties.","responses":{"200":{"description":"The current User. With a Bearer access token the response also carries the token's custom claims (e.g. `ciapol.com/station_id`) as top-level properties; profile fields always win over a same-named claim.","content":{"application/json":{"schema":{"type":"object","required":["id","email_verified","phone_verified","logins_count","user_metadata","app_metadata","account","createdAt"],"properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User name","nullable":true},"username":{"type":"string","description":"unique username for this user","nullable":true},"given_name":{"type":"string","description":"Given name","nullable":true},"family_name":{"type":"string","description":"Family name","nullable":true},"middle_name":{"type":"string","description":"Middle name (OIDC §5.1)","nullable":true},"nickname":{"type":"string","description":"Casual name. Distinct from given_name (e.g. \"Mike\" vs \"Michael\"). OIDC §5.1","nullable":true},"email":{"type":"string","description":"User email","nullable":true},"email_verified":{"type":"boolean","description":"true if email is verified","default":false},"email_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `email_verified` was last set. `manual` — admin flipped the flag via `POST /user/:id`. `verification_flow` — user clicked the verification link. `passwordless_otp` — user completed passwordless OTP login. `team_invite` — user clicked a team invitation link. `email_change` — user confirmed a self-service email change. `federated` — verified by the external IdP on OAuth callback. `password_reset` — user completed the recovery link that was emailed to them (the `email` channel only). `import` — the user arrived verified from another provider through `POST /user/import` (asserted by whoever ran the migration). `null` — the field was cleared (admin un-verified the email)."},"email_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `email_verified` was last flipped to true.","nullable":true},"email_change_locked_at":{"type":"string","description":"ISO 8601 timestamp of the user's last verified email change. When set, OAuth callbacks will not overwrite `email`/`email_verified` from the federated provider — the manually-chosen email wins.","nullable":true},"email_bounced_at":{"type":"string","description":"ISO 8601 timestamp of the last Postmark hard bounce / spam complaint for this address. When set, the email is treated as undeliverable and no further emails are sent to the user until the address changes.","nullable":true},"welcome_sent_at":{"type":"string","description":"ISO 8601 timestamp of the built-in welcome email send. Set once by the platform; guarantees at most one welcome per user.","nullable":true},"imported_at":{"type":"string","description":"ISO 8601 timestamp of when the user was created by `POST /user/import`. Imported users get none of the built-in new-user emails (verification, welcome, `user.created` notifications); `user.created` webhooks still fire and carry this field.","nullable":true},"passkey_prompted_at":{"type":"string","description":"ISO 8601 timestamp of the last time the hosted \"create a passkey\" offer was shown to this user after a login. Drives the snooze in `login_methods.passkey_promotion_snooze_days`.","nullable":true},"passkey_prompt_count":{"type":"integer","description":"How many times the passkey offer has been shown to this user. Capped by `login_methods.passkey_promotion_max_prompts`; reset by an administrator through `POST /user/:id/passkey-prompt/reset`."},"passkey_prompt_dismissed_at":{"type":"string","description":"ISO 8601 timestamp of the last time the user chose \"Not now\" on the passkey offer, as opposed to closing the tab.","nullable":true},"phone":{"type":"string","description":"contact phone number","nullable":true},"phone_e164":{"type":"boolean","description":"Whether `phone` is a valid E.164 number. Calculated on read, never stored: new writes are rejected unless they normalise, so a `false` here is a legacy row that no SMS can reach — fix the number, or set the account default country and write it again."},"phone_verified":{"type":"boolean","description":"phone is verified"},"phone_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `phone_verified` was last set. Same enum as `email_verified_method`, plus `sms_otp` — the user typed a code sent by SMS (`POST /user/:id/verify-phone/start`)."},"phone_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `phone_verified` was last flipped to true.","nullable":true},"country_iso":{"type":"string","description":"country iso code","nullable":true},"birth_date":{"type":"string","description":"user birth_date","nullable":true},"gender":{"type":"string","description":"User gender (free-form, OIDC §5.1)","nullable":true},"zoneinfo":{"type":"string","description":"IANA time-zone name (e.g. \"Europe/Madrid\"). OIDC §5.1 — used for the `zoneinfo` claim.","nullable":true},"locale":{"type":"string","description":"user main language","nullable":true},"region":{"type":"string","description":"customer region","nullable":true},"website":{"type":"string","description":"URL of the User's personal Web page or blog (OIDC §5.1)","nullable":true},"address":{"type":"object","properties":{"formatted":{"type":"string","description":"Full mailing address, formatted for display (may contain newlines)","nullable":true},"street_address":{"type":"string","nullable":true},"locality":{"type":"string","description":"City or locality","nullable":true},"region":{"type":"string","description":"State, province, or region","nullable":true},"postal_code":{"type":"string","nullable":true},"country":{"type":"string","description":"Country name","nullable":true}},"additionalProperties":false,"nullable":true},"picture":{"type":"string","description":"User picture url","nullable":true},"logins_count":{"type":"number","description":"Total number of logins this user has performed."},"last_ip":{"type":"string","description":"Last IP address from which this user logged in","nullable":true},"last_login":{"type":"string","description":"Last date and time this user logged in (ISO_8601 format)","nullable":true},"first_language":{"type":"string","description":"Primary Accept-Language subtag seen at this user's FIRST login (e.g. \"zh\"). Written once and never updated, so a later login whose language differs is visible as `event.stats.language_changed` in actions — a cross-session signal no single request can produce.","nullable":true},"internal_metadata":{"type":"object","properties":{"last_ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}},"signup":{"type":"object","required":["at"],"properties":{"ip":{"type":"string"},"user_agent":{"type":"string"},"language":{"type":"string"},"at":{"type":"string","format":"date-time"},"ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}}}}},"readOnly":true,"description":"Computed by Faable Auth, never writable through the API. `last_ip_info`: country, network (ASN) and reputation of `last_ip`, resolved after each login. `signup`: the IP, browser and language of the request that created the user, so a user who signed up but never logged in is not a blank — with the same resolution in `ip_info`. IP geolocation by DB-IP (CC BY 4.0)."},"suspended":{"type":"boolean","description":"true while the user is suspended. A suspended user is rejected at every login flow, token grant, session use and management-API call.","default":false},"suspended_at":{"type":"string","description":"ISO 8601 timestamp of when `suspended` was last flipped to true.","nullable":true},"suspended_reason":{"type":"string","description":"Free-form reason recorded when the user was suspended (e.g. \"abuse: RCE payload\").","nullable":true},"user_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"User Metadata","default":{}},"app_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"App Metadata","default":{}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"UserMetadata","default":{}},"createdAt":{"type":"string","description":"User creation date"},"updatedAt":{"type":"string","description":"User updated date"}},"additionalProperties":true,"description":"The current User. With a Bearer access token the response also carries the token's custom claims (e.g. `ciapol.com/station_id`) as top-level properties; profile fields always win over a same-named claim."}}}},"401":{"description":"`mfa_pending` — A second-factor challenge is pending; answer it before continuing.\n\n`not_logged_in` — There is no signed-in session for this request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["mfa_pending","not_logged_in"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"patch":{"operationId":"session/me_update","summary":"Update current User profile","tags":["session"],"description":"Updates the profile of the User behind the current session or bearer token. Only `name`, `company_name`, `country_iso` and the custom `fields` the application declares (`profile_fields`) can be changed; `company_name` and `fields` are merged into `user_metadata` without touching its other keys. A Bearer token must belong to this Account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"company_name":{"type":"string","maxLength":100},"country_iso":{"type":"string","maxLength":3},"fields":{"type":"array","items":{"type":"object","required":["key","value"],"properties":{"key":{"type":"string","minLength":1,"maxLength":40},"value":{"type":"string","maxLength":1000,"nullable":true}},"additionalProperties":false},"maxItems":20,"description":"Custom profile fields, stored in `user_metadata[\"faable.com/fields\"]` and merged by key: keys not named here are kept, `null` removes one. Only the keys the application declares in `profile_fields` (Client, else Account) are accepted; any other key fails the whole request with `invalid_profile_field`."}},"additionalProperties":false,"description":"Profile fields the current User may edit on themselves. `company_name` and `fields` are stored in `user_metadata`, merged with whatever else is there."}}},"description":"Profile fields the current User may edit on themselves. `company_name` and `fields` are stored in `user_metadata`, merged with whatever else is there."},"responses":{"200":{"description":"The current User. With a Bearer access token the response also carries the token's custom claims (e.g. `ciapol.com/station_id`) as top-level properties; profile fields always win over a same-named claim.","content":{"application/json":{"schema":{"type":"object","required":["id","email_verified","phone_verified","logins_count","user_metadata","app_metadata","account","createdAt"],"properties":{"id":{"type":"string","description":"User ID"},"name":{"type":"string","description":"User name","nullable":true},"username":{"type":"string","description":"unique username for this user","nullable":true},"given_name":{"type":"string","description":"Given name","nullable":true},"family_name":{"type":"string","description":"Family name","nullable":true},"middle_name":{"type":"string","description":"Middle name (OIDC §5.1)","nullable":true},"nickname":{"type":"string","description":"Casual name. Distinct from given_name (e.g. \"Mike\" vs \"Michael\"). OIDC §5.1","nullable":true},"email":{"type":"string","description":"User email","nullable":true},"email_verified":{"type":"boolean","description":"true if email is verified","default":false},"email_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `email_verified` was last set. `manual` — admin flipped the flag via `POST /user/:id`. `verification_flow` — user clicked the verification link. `passwordless_otp` — user completed passwordless OTP login. `team_invite` — user clicked a team invitation link. `email_change` — user confirmed a self-service email change. `federated` — verified by the external IdP on OAuth callback. `password_reset` — user completed the recovery link that was emailed to them (the `email` channel only). `import` — the user arrived verified from another provider through `POST /user/import` (asserted by whoever ran the migration). `null` — the field was cleared (admin un-verified the email)."},"email_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `email_verified` was last flipped to true.","nullable":true},"email_change_locked_at":{"type":"string","description":"ISO 8601 timestamp of the user's last verified email change. When set, OAuth callbacks will not overwrite `email`/`email_verified` from the federated provider — the manually-chosen email wins.","nullable":true},"email_bounced_at":{"type":"string","description":"ISO 8601 timestamp of the last Postmark hard bounce / spam complaint for this address. When set, the email is treated as undeliverable and no further emails are sent to the user until the address changes.","nullable":true},"welcome_sent_at":{"type":"string","description":"ISO 8601 timestamp of the built-in welcome email send. Set once by the platform; guarantees at most one welcome per user.","nullable":true},"imported_at":{"type":"string","description":"ISO 8601 timestamp of when the user was created by `POST /user/import`. Imported users get none of the built-in new-user emails (verification, welcome, `user.created` notifications); `user.created` webhooks still fire and carry this field.","nullable":true},"passkey_prompted_at":{"type":"string","description":"ISO 8601 timestamp of the last time the hosted \"create a passkey\" offer was shown to this user after a login. Drives the snooze in `login_methods.passkey_promotion_snooze_days`.","nullable":true},"passkey_prompt_count":{"type":"integer","description":"How many times the passkey offer has been shown to this user. Capped by `login_methods.passkey_promotion_max_prompts`; reset by an administrator through `POST /user/:id/passkey-prompt/reset`."},"passkey_prompt_dismissed_at":{"type":"string","description":"ISO 8601 timestamp of the last time the user chose \"Not now\" on the passkey offer, as opposed to closing the tab.","nullable":true},"phone":{"type":"string","description":"contact phone number","nullable":true},"phone_e164":{"type":"boolean","description":"Whether `phone` is a valid E.164 number. Calculated on read, never stored: new writes are rejected unless they normalise, so a `false` here is a legacy row that no SMS can reach — fix the number, or set the account default country and write it again."},"phone_verified":{"type":"boolean","description":"phone is verified"},"phone_verified_method":{"anyOf":[{"type":"string","enum":["manual"]},{"type":"string","enum":["verification_flow"]},{"type":"string","enum":["passwordless_otp"]},{"type":"string","enum":["team_invite"]},{"type":"string","enum":["email_change"]},{"type":"string","enum":["federated"]},{"type":"string","enum":["sms_otp"]},{"type":"string","enum":["password_reset"]},{"type":"string","enum":["import"]},{"type":"null"}],"description":"How `phone_verified` was last set. Same enum as `email_verified_method`, plus `sms_otp` — the user typed a code sent by SMS (`POST /user/:id/verify-phone/start`)."},"phone_verified_at":{"type":"string","description":"ISO 8601 timestamp of when `phone_verified` was last flipped to true.","nullable":true},"country_iso":{"type":"string","description":"country iso code","nullable":true},"birth_date":{"type":"string","description":"user birth_date","nullable":true},"gender":{"type":"string","description":"User gender (free-form, OIDC §5.1)","nullable":true},"zoneinfo":{"type":"string","description":"IANA time-zone name (e.g. \"Europe/Madrid\"). OIDC §5.1 — used for the `zoneinfo` claim.","nullable":true},"locale":{"type":"string","description":"user main language","nullable":true},"region":{"type":"string","description":"customer region","nullable":true},"website":{"type":"string","description":"URL of the User's personal Web page or blog (OIDC §5.1)","nullable":true},"address":{"type":"object","properties":{"formatted":{"type":"string","description":"Full mailing address, formatted for display (may contain newlines)","nullable":true},"street_address":{"type":"string","nullable":true},"locality":{"type":"string","description":"City or locality","nullable":true},"region":{"type":"string","description":"State, province, or region","nullable":true},"postal_code":{"type":"string","nullable":true},"country":{"type":"string","description":"Country name","nullable":true}},"additionalProperties":false,"nullable":true},"picture":{"type":"string","description":"User picture url","nullable":true},"logins_count":{"type":"number","description":"Total number of logins this user has performed."},"last_ip":{"type":"string","description":"Last IP address from which this user logged in","nullable":true},"last_login":{"type":"string","description":"Last date and time this user logged in (ISO_8601 format)","nullable":true},"first_language":{"type":"string","description":"Primary Accept-Language subtag seen at this user's FIRST login (e.g. \"zh\"). Written once and never updated, so a later login whose language differs is visible as `event.stats.language_changed` in actions — a cross-session signal no single request can produce.","nullable":true},"internal_metadata":{"type":"object","properties":{"last_ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}},"signup":{"type":"object","required":["at"],"properties":{"ip":{"type":"string"},"user_agent":{"type":"string"},"language":{"type":"string"},"at":{"type":"string","format":"date-time"},"ip_info":{"type":"object","required":["ip","resolved_at"],"properties":{"ip":{"type":"string"},"country_code":{"type":"string"},"country_name":{"type":"string"},"asn":{"type":"integer"},"as_org":{"type":"string"},"datacenter":{"type":"boolean"},"apple_private_relay":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"}}}}}},"readOnly":true,"description":"Computed by Faable Auth, never writable through the API. `last_ip_info`: country, network (ASN) and reputation of `last_ip`, resolved after each login. `signup`: the IP, browser and language of the request that created the user, so a user who signed up but never logged in is not a blank — with the same resolution in `ip_info`. IP geolocation by DB-IP (CC BY 4.0)."},"suspended":{"type":"boolean","description":"true while the user is suspended. A suspended user is rejected at every login flow, token grant, session use and management-API call.","default":false},"suspended_at":{"type":"string","description":"ISO 8601 timestamp of when `suspended` was last flipped to true.","nullable":true},"suspended_reason":{"type":"string","description":"Free-form reason recorded when the user was suspended (e.g. \"abuse: RCE payload\").","nullable":true},"user_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"User Metadata","default":{}},"app_metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"App Metadata","default":{}},"account":{"type":"string","description":"Object is related with this account"},"metadata":{"type":"object","properties":{},"additionalProperties":true,"description":"UserMetadata","default":{}},"createdAt":{"type":"string","description":"User creation date"},"updatedAt":{"type":"string","description":"User updated date"}},"additionalProperties":true,"description":"The current User. With a Bearer access token the response also carries the token's custom claims (e.g. `ciapol.com/station_id`) as top-level properties; profile fields always win over a same-named claim."}}}},"400":{"description":"`bad_request` — The request is malformed. `message` says what is wrong.\n\n`invalid_profile_field` — A profile field is not declared for this application, or its value does not match the declaration. `message` names the key.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["bad_request","invalid_profile_field","validation_error"]}}}]}}}},"401":{"description":"`mfa_pending` — A second-factor challenge is pending; answer it before continuing.\n\n`not_logged_in` — There is no signed-in session for this request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["mfa_pending","not_logged_in"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"delete":{"operationId":"session/me_delete","summary":"Delete current User","tags":["session"],"description":"Deletes the User behind the current session or bearer token, with the same effects as the management `DELETE /user/:id`: identities, credentials, second factors and team/role memberships go with it, pending tickets lose their reference, and `user.deleted` is emitted. A Bearer token must belong to this Account and be a user token (`sub` is a User); a client_credentials token is refused. When a session cookie was used it is cleared.","responses":{"204":{"description":"The User was deleted"},"401":{"description":"`mfa_pending` — A second-factor challenge is pending; answer it before continuing.\n\n`not_logged_in` — There is no signed-in session for this request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["mfa_pending","not_logged_in"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me/sessions":{"get":{"operationId":"session/meSessions","summary":"List the current user's active sessions","tags":["session"],"description":"Every active session of the user behind the request (cookie or bearer), newest first; `current_session_id` names the one this request came through.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["results","current_session_id"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/Session"}},"current_session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The id of the session this request came through: the cookie, or the session the bearer token was issued in. Null when the token predates session ids."}}}}}},"401":{"description":"`mfa_pending` — A second-factor challenge is pending; answer it before continuing.\n\n`not_logged_in` — There is no signed-in session for this request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["mfa_pending","not_logged_in"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me/sessions/revoke-others":{"post":{"operationId":"session/meRevokeOtherSessions","summary":"Sign the current user out everywhere else","tags":["session"],"description":"Revokes every active session of the user except the one this request came through. Refresh tokens issued in the revoked sessions are refused on their next use; access and id tokens already issued keep working until they expire.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["revoked"],"properties":{"revoked":{"type":"number","description":"How many sessions were ended."}}}}}},"401":{"description":"`mfa_pending` — A second-factor challenge is pending; answer it before continuing.\n\n`not_logged_in` — There is no signed-in session for this request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["mfa_pending","not_logged_in"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me/credentials":{"get":{"operationId":"session/me_credentials","summary":"How the current User signs in","tags":["session"],"description":"Whether the User behind the session or bearer token has a password, and which connections they are linked to. A Bearer token must belong to this Account and be a user token.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["has_password","connections"],"properties":{"has_password":{"type":"boolean","description":"Whether the user can sign in with a password: they have a `database` credential with a password set."},"connections":{"type":"array","items":{"type":"object","required":["id","name","type"],"properties":{"id":{"type":"string","description":"The Connection id (`connection_xxx`)."},"name":{"type":"string","description":"The Connection name."},"type":{"type":"string","description":"The Connection type: `database`, `google`, `github`, …"}},"additionalProperties":false},"description":"Every connection the user is linked to: the database one when they have a password, and each social or enterprise identity. Never the provider tokens or profile."}}}}}},"401":{"description":"`mfa_pending` — A second-factor challenge is pending; answer it before continuing.\n\n`not_logged_in` — There is no signed-in session for this request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["mfa_pending","not_logged_in"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me/identities/{connection_id}":{"delete":{"operationId":"session/me_unlink_identity","summary":"Unlink a social or enterprise account from the current User","tags":["session"],"description":"Removes the identities that link the User behind the session or bearer token to a connection (e.g. their GitHub account), with the provider tokens they hold. Refused with `last_sign_in_method` when the User would be left with no way to sign in: no password, no identity on another connection, and no passwordless email sign-in. The User's sessions stay. A Bearer token must belong to this Account and be a user token.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":64},"in":"path","name":"connection_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["unlinked"],"properties":{"unlinked":{"type":"number","description":"How many identities of the user on that connection were removed."}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"401":{"description":"`mfa_pending` — A second-factor challenge is pending; answer it before continuing.\n\n`not_logged_in` — There is no signed-in session for this request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["mfa_pending","not_logged_in"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden"]}}}]}}}},"404":{"description":"`not_found` — The resource does not exist in this account.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_found","account_not_found"]}}}]}}}},"409":{"description":"`last_sign_in_method` — Unlinking this account would leave the user with no way to sign in: set a password, verify the email or link another account first.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["last_sign_in_method"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/me/password":{"post":{"operationId":"session/me_password","summary":"Set or change the current User password","tags":["session"],"description":"Sets the password of the User behind the session or bearer token, creating their `database` credential if they have none (e.g. they signed up with Google). Changing an existing password needs `current_password`. Setting the first one needs a verified email and a recent sign-in: `auth_time` within 5 minutes and, when the User has a second factor, a sign-in that completed it (`acr` = `urn:faable:loa:2`); otherwise `reauth_required`, and the app signs in again with `max_age=0`. The connection's password policy applies. Every other session of the User is revoked, and the User gets a security email. A Bearer token must belong to this Account and be a user token.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"password":{"type":"string","minLength":1,"maxLength":512},"current_password":{"type":"string","maxLength":512}},"additionalProperties":false}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["revoked_sessions"],"properties":{"revoked_sessions":{"type":"number","description":"How many other sessions of the user were ended."}}}}}},"400":{"description":"`invalid_current_password` — The current password is wrong.\n\n`password_too_weak` — The password does not meet the connection policy. `message` lists each unmet rule.\n\n`no_database_connection` — The tenant has no database connection, so there is nowhere to store a password. Create one and retry.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_current_password","password_too_weak","no_database_connection","validation_error"]}}}]}}}},"401":{"description":"`mfa_pending` — A second-factor challenge is pending; answer it before continuing.\n\n`not_logged_in` — There is no signed-in session for this request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["mfa_pending","not_logged_in"]}}}]}}}},"403":{"description":"`forbidden` — The credentials are valid but do not allow this operation.\n\n`reauth_required` — The operation needs a recent sign-in (and the second factor, when the user has one). Sign in again with `max_age=0` and retry with the new token.\n\n`email_not_verified` — The user's email is not verified, and the operation needs it to be.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["forbidden","reauth_required","email_not_verified"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"409":{"description":"`conflict` — The request conflicts with the current state of the resource.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["conflict"]}}}]}}}},"429":{"description":"`too_many_login_attempts` — Too many failed sign-in attempts for this email or username from this network. Wait a few minutes and try again.\n\n`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_login_attempts","too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/logout":{"get":{"operationId":"session/logout","summary":"OpenID Connect RP-Initiated Logout","tags":["session"],"description":"Destroys the current session. Implements OIDC RP-Initiated Logout 1.0 and Front-Channel Logout 1.0: when RPs with a registered `frontchannel_logout_uri` participated in the session, returns an HTML page with one iframe per RP and (when applicable) auto-redirects to `post_logout_redirect_uri`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"id_token_hint","required":false,"description":"Previously issued id_token. RECOMMENDED per OIDC §3 — used to identify the client/session being logged out."},{"schema":{"type":"string"},"in":"query","name":"logout_hint","required":false,"description":"Hint about the End-User that is logging out (e.g. session id, email). Provider-specific."},{"schema":{"type":"string"},"in":"query","name":"client_id","required":false,"description":"OAuth Client identifier. Derived from `id_token_hint.aud` if omitted."},{"schema":{"type":"string"},"in":"query","name":"post_logout_redirect_uri","required":false,"description":"URL to redirect the User to after logout. MUST be pre-registered in the client `logout_urls` (exact match)."},{"schema":{"type":"string"},"in":"query","name":"state","required":false,"description":"Opaque value echoed back as `state` query param on the post-logout redirect, to mitigate CSRF."},{"schema":{"type":"string"},"in":"query","name":"ui_locales","required":false,"description":"Space-separated list of preferred UI languages."},{"schema":{"type":"string"},"in":"query","name":"returnTo","required":false,"description":"Deprecated alias of `post_logout_redirect_uri`. Kept for backwards compatibility."}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_redirect_uri` — The redirect URI is not registered for the client.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_redirect_uri","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/logout/confirm":{"post":{"operationId":"session/logoutConfirm","summary":"Confirm a parked RP-Initiated Logout","tags":["session"],"description":"Finishes a `/logout` request that was parked pending confirmation (backlog §240) because it carried no verified `id_token_hint`. Single-use: the state cannot be replayed.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state"],"properties":{"state":{"type":"string","description":"The `state` the confirmation screen was opened with."}}}}}},"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_state","validation_error"]}}}]}}}},"401":{"description":"`state_mismatch` — The `state` belongs to another account, session, client or ceremony.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["state_mismatch"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/authorize":{"get":{"operationId":"authorize","summary":"Authorization request","tags":["oauth"],"description":"Authorization request","parameters":[{"schema":{"type":"string"},"in":"query","name":"response_type","required":false},{"schema":{"type":"string"},"in":"query","name":"code_challenge_method","required":false,"description":"Challenge method for PKCE. Only S256 is supported."},{"schema":{"type":"string"},"in":"query","name":"code_challenge","required":false,"description":"Challenge for PKCE authorization code flow"},{"schema":{"type":"string"},"in":"query","name":"redirect_to","required":false,"description":"Same-origin path to return the user to after authentication, instead of the OAuth client redirect. Used by first-party interactive flows (e.g. device-code confirm). Non same-origin values are ignored."},{"schema":{"type":"string"},"in":"query","name":"connection_scope","required":false,"description":"Space-separated extra scopes to request from the upstream identity provider (Auth0-compatible), added to the ones configured in the connection. The `scope` parameter describes the tokens Faable issues and is never forwarded to the provider."},{"schema":{"type":"boolean"},"in":"query","name":"link","required":false,"description":"Link mode: attach the resulting provider identity to the ACTIVE session user instead of signing in or up. Requires a session; incompatible with prompt=login/consent."},{"schema":{"type":"string"},"examples":{"connection_6475d947ea96eb57c2062c90":{"value":"connection_6475d947ea96eb57c2062c90"},"email,passkey":{"value":"email,passkey"}},"in":"query","name":"login_methods","required":false,"description":"Comma-separated methods the hosted login screen shows for this login: connection ids or connection names, and `passkey`. Each must be one the client already offers; the screen keeps the order configured for the client. Filters the screen only — it does not restrict which method the credential endpoints accept. Not combinable with `connection`, which skips the screen instead of narrowing it."},{"schema":{"anyOf":[{"type":"string","enum":["light"]},{"type":"string","enum":["dark"]}]},"example":"dark","in":"query","name":"theme","required":false,"description":"Force the colour theme of the hosted login screens for this login — for an application whose own pages are only light or only dark. Overrides the tenant's `login_methods.theme` and hides the visitor's theme switch on those screens. Omit it to keep the tenant setting."},{"schema":{"type":"string"},"in":"query","name":"flow_preview","required":false,"description":"Preview token from `POST /loginflow/:id/preview-token`. This login runs the DRAFT of that flow, for this browser only; nothing is published. Invalid or expired tokens are ignored."},{"schema":{"type":"string"},"in":"query","name":"resume_state","required":false,"description":"Resume the login transaction the hosted screen has parked, instead of starting a new one. Used by the screen itself when the person picks a method: everything that belongs to the Relying Party — `code_challenge`, `state`, `redirect_uri`, `nonce`, `scope`, `audience` — is taken from the parked transaction and CANNOT be overridden by the query. Only `connection` is read from the caller."},{"schema":{"type":"string"},"in":"query","name":"link_ticket","required":false,"description":"Single-use ticket from `POST /oauth/link_ticket`. With `link=true`, establishes the cookie session for the ticket’s user before linking — for Relying Parties that hold tokens but no cookie session (direct grants such as passwordless OTP). Invalid or expired tickets are ignored."},{"schema":{"type":"string"},"in":"query","name":"client_id","required":true},{"schema":{"type":"string"},"in":"query","name":"connection","required":false,"description":"Deprecated. Use connection_id"},{"schema":{"type":"string"},"in":"query","name":"connection_id","required":false,"description":"Which oauth connection to use. Leave empty to use default connection"},{"schema":{"type":"string"},"in":"query","name":"scope","required":false},{"schema":{"type":"string"},"in":"query","name":"redirect_uri","required":false,"description":"The URL to which Faable will redirect the browser after authorization has been granted by the user."},{"schema":{"type":"string"},"in":"query","name":"state","required":false,"description":"An opaque value the applications adds to the initial request that the authorization server includes when redirecting the back to the application. This value must be used by the application to prevent CSRF attacks."},{"schema":{"type":"string"},"in":"query","name":"nonce","required":false,"description":"OIDC §3.1.2.1 — string value used to associate a Client session with an ID Token, to mitigate replay attacks. If passed at /authorize, the same value MUST appear in the issued id_token."},{"schema":{"type":"number","minimum":0},"in":"query","name":"max_age","required":false,"description":"OIDC §3.1.2.1 — maximum allowable elapsed time in seconds since the last End-User authentication. If exceeded, the OP re-authenticates and `auth_time` is REQUIRED in the resulting id_token."},{"schema":{"type":"string"},"in":"query","name":"prompt","required":false},{"schema":{"type":"string"},"in":"query","name":"login_hint","required":false,"description":"OIDC §3.1.2.1 — hint about the identifier (email, username) the End-User will sign in with. Pre-fills the hosted login screen and is forwarded to upstream identity providers."},{"schema":{"type":"string"},"in":"query","name":"id_token_hint","required":false,"description":"OIDC §3.1.2.1 — an id_token this provider issued earlier for the user the client believes is signed in. Verified (signature, not expiry). With `prompt=none`, a session for a different user answers `login_required`; otherwise it forces a fresh sign-in."},{"schema":{"type":"string"},"in":"query","name":"acr_values","required":false,"description":"OIDC §3.1.2.1 — space-separated Authentication Context Class References, in order of preference. `urn:faable:loa:2` asks for a second factor: a session that only has one is challenged (or, with `prompt=none`, answers `interaction_required`)."},{"schema":{"type":"string"},"in":"query","name":"ui_locales","required":false,"description":"OIDC §3.1.2.1 — preferred languages for the hosted screens, space-separated BCP47 tags. Forwarded to upstream identity providers."},{"schema":{"type":"string"},"in":"query","name":"audience","required":false,"description":"Auth0-compatible alias for `resource` (RFC 8707). When both are sent they must match. Identifier of the Api the client wants to access; persisted in the auth state so the token issued at /oauth/token targets the matching Api resource."},{"schema":{"type":"string"},"in":"query","name":"resource","required":false,"description":"RFC 8707 Resource Indicator. MUST be an absolute URI without fragment. When matched against a registered Api in the tenant the issued access_token gets `aud = <api.identifier>`."}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_client` — The `client_id` in the request does not name a client of this account.\n\n`invalid_connection` — The connection does not exist, is disabled, or is not of the type this flow needs.\n\n`invalid_redirect_uri` — The redirect URI is not registered for the client.\n\n`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_client","invalid_connection","invalid_redirect_uri","invalid_state","validation_error"]}}}]}}}},"401":{"description":"`state_mismatch` — The `state` belongs to another account, session, client or ceremony.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["state_mismatch"]}}}]}}}},"403":{"description":"`origin_not_allowed` — The request `Origin` is not in the client's Allowed Web Origins.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["origin_not_allowed"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`login_flow_misconfigured` — The login flow of this account cannot run. `message` carries the node error.\n\n`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["login_flow_misconfigured","internal_error"]}}}]}}}}}}},"/select-account":{"post":{"operationId":"oauth/selectAccount","summary":"Answer the account chooser of a prompt=select_account login","tags":["oauth"],"description":"Resumes an authorization request that `/authorize` parked on the hosted account chooser. Requires the session cookie of the user the chooser was shown to. Returns where the browser goes next: the client callback with a code, or a login screen.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state","choice"],"properties":{"state":{"type":"string","description":"The `state` the select-account screen was opened with."},"choice":{"anyOf":[{"type":"string","enum":["continue"]},{"type":"string","enum":["other"]}],"description":"`continue`: go on as the signed-in user. `other`: sign this browser out and start a fresh login for the same request."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["redirect_url"],"properties":{"redirect_url":{"type":"string"}}}}}},"400":{"description":"`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_state","validation_error"]}}}]}}}},"401":{"description":"`not_logged_in` — There is no signed-in session for this request.\n\n`state_mismatch` — The `state` belongs to another account, session, client or ceremony.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_logged_in","state_mismatch"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/oauth/link_ticket":{"post":{"operationId":"oauth/link_ticket","summary":"Mint a link ticket","tags":["oauth"],"description":"Trades the Bearer access token of a signed-in user for a single-use, short-lived ticket that lets `/authorize?link=true` establish the cookie session it links the new identity to. For Relying Parties that authenticated the user with a direct grant (e.g. passwordless OTP) and therefore hold no cookie session. Subject to the client’s `web_origins` allowlist like `/oauth/token`.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["link_ticket","expires_in"],"properties":{"link_ticket":{"type":"string","description":"Opaque single-use ticket. Pass it as `link_ticket` on `/authorize?link=true` within `expires_in` seconds."},"expires_in":{"type":"number","description":"Ticket lifetime in seconds."}}}}}},"401":{"description":"`invalid_token` — The bearer token is missing, malformed, expired or has no subject.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_token"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/oauth/session-from-ticket":{"get":{"operationId":"oauth/sessionFromTicket","summary":"Open the hosted session from a ticket","tags":["oauth"],"description":"Consumes a ticket from `POST /oauth/link_ticket`, establishes the cookie session for its user and redirects to `redirect_to`, a hosted screen of this host. For Relying Parties that signed the user in with a direct grant (e.g. passwordless OTP) and so hold no cookie session, when they send the user to an account screen such as `/flow/account/security`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"ticket","required":true,"description":"A ticket from `POST /oauth/link_ticket`, single use."},{"schema":{"type":"string"},"in":"query","name":"redirect_to","required":true,"description":"Where to go once the session is open: a path of this host under `/flow/` (e.g. `/flow/account/security?client_id=…`), or — with `client_id` — a URL on one of that client’s own origins (its `web_origins` or the origin of a registered callback)."},{"schema":{"type":"string"},"in":"query","name":"client_id","required":false,"description":"The OAuth `client_id` the ticket was minted for. Required when `redirect_to` leaves `/flow/`: it is what the return URL is checked against, before the ticket is spent."}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_request` — A required OAuth parameter is missing or two of them are incompatible. `message` says which.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_request","validation_error"]}}}]}}}},"403":{"description":"`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/oauth/token":{"get":{"operationId":"oauth/tokenGet","summary":"OAuth2 Token endpoint (GET)","tags":["oauth"],"description":"OAuth2 Token endpoint (RFC 6749 §3.2). Exchanges credentials for tokens depending on `grant_type` (authorization_code, client_credentials, refresh_token, password, otp, token_exchange, device_code). The GET variant accepts the same parameters as querystring.","parameters":[{"schema":{"anyOf":[{"type":"string","enum":["authorization_code"]},{"type":"string","enum":["client_credentials"]},{"type":"string","enum":["refresh_token"]},{"type":"string","enum":["password"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-otp"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-recovery-code"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/passwordless/otp"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:token-exchange"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:device_code"]}]},"in":"query","name":"grant_type","required":false,"description":"Grant Type. https://oauth.net/2/grant-types/"},{"schema":{"type":"string"},"in":"query","name":"client_id","required":false},{"schema":{"type":"string"},"in":"query","name":"client_secret","required":false},{"schema":{"type":"string"},"in":"query","name":"scope","required":false},{"schema":{"type":"string"},"in":"query","name":"code","required":false},{"schema":{"type":"string"},"in":"query","name":"code_verifier","required":false},{"schema":{"type":"string"},"in":"query","name":"username","required":false},{"schema":{"type":"string"},"in":"query","name":"password","required":false},{"schema":{"type":"string"},"in":"query","name":"realm","required":false,"description":"For `grant_type=password`: the name of the database connection to sign in against. Defaults to the first database connection offered to the client."},{"schema":{"type":"string"},"in":"query","name":"otp","required":false},{"schema":{"type":"string"},"in":"query","name":"refresh_token","required":false},{"schema":{"type":"string"},"in":"query","name":"mfa_token","required":false,"description":"Opaque token returned with a `403 mfa_required` response. Present it here with `grant_type=http://auth0.com/oauth/grant-type/mfa-otp` (or `…/mfa-recovery-code`) and the code, to finish a grant a second-factor policy interrupted."},{"schema":{"type":"string"},"in":"query","name":"recovery_code","required":false,"description":"One of the user's one-time recovery codes, for `grant_type=http://auth0.com/oauth/grant-type/mfa-recovery-code`."},{"schema":{"type":"string"},"in":"query","name":"subject_token","required":false},{"schema":{"type":"string"},"in":"query","name":"subject_token_type","required":false},{"schema":{"type":"string"},"in":"query","name":"device_code","required":false,"description":"The device code returned by the authorization server."},{"schema":{"type":"string"},"in":"query","name":"audience","required":false,"description":"Auth0-compatible alias for `resource` (RFC 8707). When both are provided they must match."},{"schema":{"type":"string"},"in":"query","name":"resource","required":false,"description":"RFC 8707 Resource Indicator. Absolute URI of the target Api, no fragment. Matched against `Api.identifier` in the tenant."},{"schema":{"type":"string"},"in":"query","name":"invalidate_token","required":false,"description":"client_credentials only. An access token previously returned by this endpoint that a resource server rejected with 401. If it is the cached token for this client, the cache entry is dropped and a new token is issued; otherwise it has no effect."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["token_type","expires_in","access_token"],"properties":{"token_type":{"type":"string"},"expires_in":{"type":"number"},"access_token":{"type":"string"},"refresh_token":{"type":"string"},"id_token":{"type":"string"},"scope":{"type":"string"},"issued_token_type":{"type":"string"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"oauth/token","summary":"OAuth2 Token endpoint","tags":["oauth"],"description":"OAuth2 Token endpoint (RFC 6749 §3.2). Exchanges credentials for tokens depending on `grant_type` (authorization_code, client_credentials, refresh_token, password, otp, token_exchange, device_code).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"grant_type":{"anyOf":[{"type":"string","enum":["authorization_code"]},{"type":"string","enum":["client_credentials"]},{"type":"string","enum":["refresh_token"]},{"type":"string","enum":["password"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-otp"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-recovery-code"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/passwordless/otp"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:token-exchange"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:device_code"]}],"description":"Grant Type. https://oauth.net/2/grant-types/"},"client_id":{"type":"string"},"client_secret":{"type":"string"},"scope":{"type":"string"},"code":{"type":"string"},"code_verifier":{"type":"string"},"username":{"type":"string"},"password":{"type":"string"},"realm":{"type":"string","description":"For `grant_type=password`: the name of the database connection to sign in against. Defaults to the first database connection offered to the client."},"otp":{"type":"string"},"refresh_token":{"type":"string"},"mfa_token":{"type":"string","description":"Opaque token returned with a `403 mfa_required` response. Present it here with `grant_type=http://auth0.com/oauth/grant-type/mfa-otp` (or `…/mfa-recovery-code`) and the code, to finish a grant a second-factor policy interrupted."},"recovery_code":{"type":"string","description":"One of the user's one-time recovery codes, for `grant_type=http://auth0.com/oauth/grant-type/mfa-recovery-code`."},"subject_token":{"type":"string"},"subject_token_type":{"type":"string"},"device_code":{"type":"string","description":"The device code returned by the authorization server."},"audience":{"type":"string","description":"Auth0-compatible alias for `resource` (RFC 8707). When both are provided they must match."},"resource":{"type":"string","description":"RFC 8707 Resource Indicator. Absolute URI of the target Api, no fragment. Matched against `Api.identifier` in the tenant."},"invalidate_token":{"type":"string","description":"client_credentials only. An access token previously returned by this endpoint that a resource server rejected with 401. If it is the cached token for this client, the cache entry is dropped and a new token is issued; otherwise it has no effect."}},"additionalProperties":true}}}},"parameters":[{"schema":{"anyOf":[{"type":"string","enum":["authorization_code"]},{"type":"string","enum":["client_credentials"]},{"type":"string","enum":["refresh_token"]},{"type":"string","enum":["password"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-otp"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/mfa-recovery-code"]},{"type":"string","enum":["http://auth0.com/oauth/grant-type/passwordless/otp"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:token-exchange"]},{"type":"string","enum":["urn:ietf:params:oauth:grant-type:device_code"]}]},"in":"query","name":"grant_type","required":false,"description":"Grant Type. https://oauth.net/2/grant-types/"},{"schema":{"type":"string"},"in":"query","name":"client_id","required":false},{"schema":{"type":"string"},"in":"query","name":"client_secret","required":false},{"schema":{"type":"string"},"in":"query","name":"scope","required":false},{"schema":{"type":"string"},"in":"query","name":"code","required":false},{"schema":{"type":"string"},"in":"query","name":"code_verifier","required":false},{"schema":{"type":"string"},"in":"query","name":"username","required":false},{"schema":{"type":"string"},"in":"query","name":"password","required":false},{"schema":{"type":"string"},"in":"query","name":"realm","required":false,"description":"For `grant_type=password`: the name of the database connection to sign in against. Defaults to the first database connection offered to the client."},{"schema":{"type":"string"},"in":"query","name":"otp","required":false},{"schema":{"type":"string"},"in":"query","name":"refresh_token","required":false},{"schema":{"type":"string"},"in":"query","name":"mfa_token","required":false,"description":"Opaque token returned with a `403 mfa_required` response. Present it here with `grant_type=http://auth0.com/oauth/grant-type/mfa-otp` (or `…/mfa-recovery-code`) and the code, to finish a grant a second-factor policy interrupted."},{"schema":{"type":"string"},"in":"query","name":"recovery_code","required":false,"description":"One of the user's one-time recovery codes, for `grant_type=http://auth0.com/oauth/grant-type/mfa-recovery-code`."},{"schema":{"type":"string"},"in":"query","name":"subject_token","required":false},{"schema":{"type":"string"},"in":"query","name":"subject_token_type","required":false},{"schema":{"type":"string"},"in":"query","name":"device_code","required":false,"description":"The device code returned by the authorization server."},{"schema":{"type":"string"},"in":"query","name":"audience","required":false,"description":"Auth0-compatible alias for `resource` (RFC 8707). When both are provided they must match."},{"schema":{"type":"string"},"in":"query","name":"resource","required":false,"description":"RFC 8707 Resource Indicator. Absolute URI of the target Api, no fragment. Matched against `Api.identifier` in the tenant."},{"schema":{"type":"string"},"in":"query","name":"invalidate_token","required":false,"description":"client_credentials only. An access token previously returned by this endpoint that a resource server rejected with 401. If it is the cached token for this client, the cache entry is dropped and a new token is issued; otherwise it has no effect."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["token_type","expires_in","access_token"],"properties":{"token_type":{"type":"string"},"expires_in":{"type":"number"},"access_token":{"type":"string"},"refresh_token":{"type":"string"},"id_token":{"type":"string"},"scope":{"type":"string"},"issued_token_type":{"type":"string"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/oauth/device/code":{"post":{"operationId":"oauth/deviceCode","summary":"Device authorization request","tags":["oauth"],"description":"OAuth2 Device Authorization Grant (RFC 8628) — start step. A device that cannot run a browser calls this endpoint to obtain a `device_code`, a `user_code` to show to the user, and the verification URLs to display.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["client_id"],"properties":{"client_id":{"type":"string","description":"The client ID of the application."},"scope":{"type":"string","description":"The scope of the access request."},"audience":{"type":"string","description":"Identifier of the API the device wants to access. Resolved against the tenant Api resources (`Api.identifier`) at token issuance."},"device_name":{"type":"string","description":"Human-friendly name of the requesting device (e.g. \"marcs-mbp (macOS)\"). Shown on the confirm page so the user can recognise the device being authorised.","maxLength":200}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["device_code","user_code","verification_uri","verification_uri_complete","expires_in","interval"],"properties":{"device_code":{"type":"string"},"user_code":{"type":"string"},"verification_uri":{"type":"string"},"verification_uri_complete":{"type":"string"},"expires_in":{"type":"number"},"interval":{"type":"number"}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`client_not_found` — No client with that `client_id`.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["client_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/activate":{"get":{"operationId":"oauth/deviceActivate","summary":"Device activation redirect","tags":["oauth"],"description":"Entry URL displayed on the device. Redirects the browser to the external `/flow/device-code/activate` UI where the user enters their `user_code`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"user_code","required":false}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_device_code` — The device or user code is invalid or expired.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_device_code","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/confirm":{"get":{"operationId":"oauth/deviceConfirm","summary":"Device confirmation redirect","tags":["oauth"],"description":"Redirects to the external `/flow/device-code/confirm` UI after the user enters their `user_code`. The UI then calls `/oauth/device/authorize` to grant the device.","parameters":[{"schema":{"type":"string"},"in":"query","name":"user_code","required":false}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/oauth/device/verify":{"post":{"operationId":"oauth/deviceVerify","summary":"Verify a device user_code","tags":["oauth"],"description":"Called by the external UI after the user enters their `user_code`. Returns the matching `device_code` and minimal client info so the UI can render \"Authorize <App>?\".","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_code"],"properties":{"user_code":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["device_code","client"],"properties":{"device_code":{"type":"string"},"device_name":{"type":"string"},"client":{"type":"object","properties":{"name":{"type":"string"},"client_id":{"type":"string"}}}}}}}},"400":{"description":"`invalid_device_code` — The device or user code is invalid or expired.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_device_code","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/oauth/device/authorize":{"post":{"operationId":"oauth/deviceAuthorize","summary":"Authorize a pending device request","tags":["oauth"],"description":"Authorizes a `device_code` on behalf of the currently authenticated user, allowing the device to subsequently exchange that code for tokens at `/oauth/token`. Called by the first-party device-code confirm page, so it authenticates with the browser `faable_sess` session — NOT a bearer token (the page has no access token to send).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["device_code"],"properties":{"device_code":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean"}}}}}},"400":{"description":"`invalid_device_code` — The device or user code is invalid or expired.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_device_code","validation_error"]}}}]}}}},"401":{"description":"`not_logged_in` — There is no signed-in session for this request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["not_logged_in"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/callback":{"get":{"operationId":"callback","summary":"OAuth2 Callback URL","tags":["oauth"],"description":"OAuth2 Callback URL","parameters":[{"schema":{"type":"string"},"in":"query","name":"code","required":false},{"schema":{"type":"string"},"in":"query","name":"state","required":false},{"schema":{"type":"string"},"in":"query","name":"error","required":false},{"schema":{"type":"string"},"in":"query","name":"error_uri","required":false},{"schema":{"type":"string"},"in":"query","name":"error_description","required":false}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`connection_required` — No connection was given and the client has no `default_connection`.\n\n`identity_orphaned` — The identity pointed at a user that no longer exists; it was removed. Sign in again.\n\n`invalid_request` — A required OAuth parameter is missing or two of them are incompatible. `message` says which.\n\n`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`provider_error` — The upstream identity provider returned an error. The suffix is the provider’s own code, when it sent one: `provider_error:bad_refresh_token` (the user must re-authorize), `provider_error:incorrect_client_credentials` (our configuration, the user can do nothing). `details.provider_error_description` carries the provider’s sentence.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["connection_required","identity_orphaned","invalid_request","invalid_state","provider_error","validation_error"]}}}]}}}},"401":{"description":"`login_denied` — A post-login Action denied the sign-in. `message` carries the reason it gave. On token grants it is a 403 with `error: invalid_grant`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["login_denied"]}}}]}}}},"403":{"description":"`mfa_required` — The login needs a second factor that this flow cannot collect, or one is still pending on the session.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["mfa_required","user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"409":{"description":"`identity_already_linked` — That external identity is already linked to another user.\n\n`identity_conflict` — The user already has a linked identity for this connection.\n\n`identity_link_denied` — That external identity cannot be linked to this user.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["identity_already_linked","identity_conflict","identity_link_denied"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"callbackFormPost","summary":"OAuth2 Callback URL (form_post)","tags":["oauth"],"description":"Callback for providers that use `response_mode=form_post` (Sign in with Apple). Redirects (303) to `GET /callback` with the same `code` and `state`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"state":{"type":"string"},"error":{"type":"string"},"error_uri":{"type":"string"},"error_description":{"type":"string"},"user":{"type":"string","maxLength":4096}}}}}},"responses":{"200":{"description":"Default Response"},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/continue":{"get":{"operationId":"oauth_continue","summary":"OAuth2 Resume URL","tags":["oauth"],"description":"Resume OAuth flow after a progressive profiling action","parameters":[{"schema":{"type":"string","minLength":1},"in":"query","name":"state","required":true}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_state","validation_error"]}}}]}}}},"401":{"description":"`action_unavailable` — The Action that paused this login is disabled or belongs elsewhere.\n\n`login_denied` — A post-login Action denied the sign-in. `message` carries the reason it gave. On token grants it is a 403 with `error: invalid_grant`.\n\n`state_mismatch` — The `state` belongs to another account, session, client or ceremony.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["action_unavailable","login_denied","state_mismatch"]}}}]}}}},"403":{"description":"`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/.well-known/openid-configuration":{"get":{"operationId":"oauth/openidConfiguration","summary":"OpenID Connect Discovery document","tags":["oauth"],"description":"Returns the OpenID Provider Configuration document for this Account (issuer, supported endpoints, response types, signing algorithms, claims, etc.) as defined by OIDC Discovery 1.0.","responses":{"200":{"description":"Default Response"},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/.well-known/oauth-authorization-server":{"get":{"operationId":"oauth/authorizationServerMetadata","summary":"OAuth 2.0 Authorization Server Metadata (RFC 8414)","tags":["oauth"],"description":"The same provider metadata as the OpenID Connect Discovery document, at the RFC 8414 location that OAuth 2.0 and MCP clients query.","responses":{"200":{"description":"Default Response"},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/.well-known/jwks.json":{"get":{"operationId":"oauth/jwks","summary":"JSON Web Key Set","tags":["oauth"],"description":"Returns the public JWKs (RFC 7517) used to verify tokens issued by this Account. Consumers MUST use the `kid` from a token header to select the right key.","responses":{"200":{"description":"Default Response"},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/userinfo":{"get":{"operationId":"oauth/userinfo","summary":"OpenID Connect UserInfo endpoint","tags":["oauth"],"description":"Returns claims about the authenticated User. Authentication is via a Bearer access token (RFC 6750). Returned claims are filtered by the `scope` claim of the access token (OIDC §5.3.2). The response always contains `sub`. See https://openid.net/specs/openid-connect-core-1_0.html#UserInfo.","responses":{"200":{"description":"OpenID Connect UserInfo response (https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse).","content":{"application/json":{"schema":{"type":"object","required":["sub"],"properties":{"sub":{"type":"string","description":"Subject identifier (user id)"},"name":{"type":"string"},"family_name":{"type":"string"},"given_name":{"type":"string"},"nickname":{"type":"string"},"preferred_username":{"type":"string"},"picture":{"type":"string"},"birthdate":{"type":"string"},"locale":{"type":"string"},"updated_at":{"type":"number"},"email":{"type":"string"},"email_verified":{"type":"boolean"},"phone_number":{"type":"string"},"phone_number_verified":{"type":"boolean"}},"additionalProperties":true,"description":"OpenID Connect UserInfo response (https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse)."}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"oauth/userinfoPost","summary":"OpenID Connect UserInfo endpoint (POST)","tags":["oauth"],"description":"Same as `GET /userinfo`. Provided for clients that prefer to send the Bearer token in a POST body or are required to by spec compliance audits.","responses":{"200":{"description":"OpenID Connect UserInfo response (https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse).","content":{"application/json":{"schema":{"type":"object","required":["sub"],"properties":{"sub":{"type":"string","description":"Subject identifier (user id)"},"name":{"type":"string"},"family_name":{"type":"string"},"given_name":{"type":"string"},"nickname":{"type":"string"},"preferred_username":{"type":"string"},"picture":{"type":"string"},"birthdate":{"type":"string"},"locale":{"type":"string"},"updated_at":{"type":"number"},"email":{"type":"string"},"email_verified":{"type":"boolean"},"phone_number":{"type":"string"},"phone_number_verified":{"type":"boolean"}},"additionalProperties":true,"description":"OpenID Connect UserInfo response (https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse)."}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/oidc/register":{"post":{"operationId":"oauth/register","summary":"OpenID Connect Dynamic Client Registration","tags":["oauth"],"description":"Register a new OAuth/OIDC client dynamically (OIDC Dynamic Client Registration 1.0 / RFC 7591). The new client is bound to the Account resolved from the request context. Only when the tenant has `dynamic_client_registration_enabled`; otherwise `registration_disabled` (403).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["redirect_uris"],"properties":{"redirect_uris":{"type":"array","items":{"type":"string","format":"uri"},"description":"Array of redirection URI values used by the client (RFC 7591 §2). REQUIRED for any client that uses an authorization grant.","minItems":1},"client_name":{"type":"string","description":"Human-readable name of the client (RFC 7591 §2)"},"client_uri":{"type":"string","description":"URL of the home page of the client"},"logo_uri":{"type":"string"},"tos_uri":{"type":"string"},"policy_uri":{"type":"string"},"contacts":{"type":"array","items":{"type":"string"},"description":"Array of email addresses responsible for the client"},"grant_types":{"type":"array","items":{"type":"string"},"description":"Grant types the client will use. Defaults to [\"authorization_code\"]. Must be a subset of what the server supports."},"response_types":{"type":"array","items":{"type":"string"},"description":"Response types the client will use. Defaults to [\"code\"]."},"token_endpoint_auth_method":{"type":"string","description":"How the client authenticates at the token endpoint. Defaults to \"client_secret_basic\"."},"application_type":{"anyOf":[{"type":"string","enum":["web"]},{"type":"string","enum":["native"]}],"description":"OIDC §2 — application type. Defaults to \"web\"."},"post_logout_redirect_uris":{"type":"array","items":{"type":"string"},"description":"OIDC RP-Initiated Logout — URIs the OP MAY redirect to after end-session."},"scope":{"type":"string","description":"Space-separated list of scope values the client may use."},"software_id":{"type":"string"},"software_version":{"type":"string"},"frontchannel_logout_uri":{"type":"string","description":"OIDC Front-Channel Logout 1.0 — URL the OP loads inside an iframe at end-session to let the RP clear its session."},"frontchannel_logout_session_required":{"type":"boolean","description":"OIDC Front-Channel Logout 1.0 — when true, the OP MUST include `iss` and `sid` parameters when calling the frontchannel_logout_uri."},"backchannel_logout_uri":{"type":"string","description":"OIDC Back-Channel Logout 1.0 — URL the OP POSTs a signed `logout_token` to when a session this client took part in ends."},"backchannel_logout_session_required":{"type":"boolean","description":"OIDC Back-Channel Logout 1.0 — when true, the `logout_token` MUST include a `sid` claim."},"web_origins":{"type":"array","items":{"type":"string"},"description":"Non-standard (Auth0-compatible) — origins (scheme://host[:port], no path) allowed to make cross-origin (CORS) requests to the token and passwordless endpoints. Empty allows any origin."}},"additionalProperties":true}}}},"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["client_id","client_id_issued_at","client_secret_expires_at","redirect_uris","grant_types","response_types","token_endpoint_auth_method","application_type"],"properties":{"client_id":{"type":"string"},"client_secret":{"type":"string"},"client_id_issued_at":{"type":"number","description":"Unix timestamp (seconds) at which the client_id was issued"},"client_secret_expires_at":{"type":"number","description":"0 if the client_secret does not expire"},"redirect_uris":{"type":"array","items":{"type":"string"}},"client_name":{"type":"string"},"client_uri":{"type":"string"},"logo_uri":{"type":"string"},"tos_uri":{"type":"string"},"policy_uri":{"type":"string"},"contacts":{"type":"array","items":{"type":"string"}},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string"},"application_type":{"type":"string"},"post_logout_redirect_uris":{"type":"array","items":{"type":"string"}},"scope":{"type":"string"},"software_id":{"type":"string"},"software_version":{"type":"string"},"frontchannel_logout_uri":{"type":"string"},"frontchannel_logout_session_required":{"type":"boolean"},"backchannel_logout_uri":{"type":"string"},"backchannel_logout_session_required":{"type":"boolean"},"web_origins":{"type":"array","items":{"type":"string"}}},"additionalProperties":true}}}},"400":{"description":"`invalid_client_metadata` — A client field was rejected: by dynamic client registration (RFC 7591), or on a client create/update (`initiate_login_uri`). `message` names it.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_client_metadata","validation_error"]}}}]}}}},"403":{"description":"`registration_disabled` — Dynamic client registration is turned off for this tenant. Create the client from the dashboard or the Management API, or turn on `dynamic_client_registration_enabled`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["registration_disabled"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/consent/context":{"get":{"summary":"What the consent screen shows","tags":["oauth"],"description":"For a login parked on the consent screen: the client asking (name, logo, where it redirects), the user, the API and permissions requested, and — when the API offers one — the choice the user has to make (e.g. which project).","parameters":[{"schema":{"type":"string"},"in":"query","name":"state","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["account","client","user","scopes"],"properties":{"account":{"type":"object","properties":{"name":{"type":"string"}}},"client":{"type":"object","required":["name","localhost_only"],"properties":{"name":{"type":"string"},"logo_uri":{"type":"string"},"client_uri":{"type":"string"},"redirect_host":{"type":"string"},"localhost_only":{"type":"boolean"},"metadata_document_url":{"type":"string"}}},"user":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"}}},"resource":{"type":"object","required":["identifier","name"],"properties":{"identifier":{"type":"string"},"name":{"type":"string"}}},"scopes":{"type":"array","items":{"type":"object","required":["value","description"],"properties":{"value":{"type":"string"},"description":{"type":"string"}}}},"choice":{"type":"object","required":["label","options"],"properties":{"label":{"type":"string"},"options":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}}}}}}}}}},"400":{"description":"`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_state","validation_error"]}}}]}}}},"401":{"description":"`state_mismatch` — The `state` belongs to another account, session, client or ceremony.\n\n`session_missing` — The session that started this login no longer exists.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["state_mismatch","session_missing"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}},"502":{"description":"`consent_options_unavailable` — The consent screen could not load the choices from the API being authorized (its `consent_options.url`). Try again in a moment.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["consent_options_unavailable"]}}}]}}}}}}},"/consent":{"post":{"summary":"Allow or deny a client on the consent screen","tags":["oauth"],"description":"Records the decision and returns where the browser goes next: on `allow`, on to the client with its code (the grant is remembered, so the screen is not shown again for the same permissions); on `deny`, back to the client with `error=access_denied`. `choice` is required when the consent context offered one, and must be one of its options.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state","decision"],"properties":{"state":{"type":"string"},"decision":{"anyOf":[{"type":"string","enum":["allow"]},{"type":"string","enum":["deny"]}]},"choice":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["redirect_url"],"properties":{"redirect_url":{"type":"string"}}}}}},"400":{"description":"`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`consent_choice_invalid` — The choice sent with the consent decision is not one the API offered for this user.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_state","consent_choice_invalid","validation_error"]}}}]}}}},"401":{"description":"`state_mismatch` — The `state` belongs to another account, session, client or ceremony.\n\n`session_missing` — The session that started this login no longer exists.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["state_mismatch","session_missing"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}},"502":{"description":"`consent_options_unavailable` — The consent screen could not load the choices from the API being authorized (its `consent_options.url`). Try again in a moment.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["consent_options_unavailable"]}}}]}}}}}}},"/oauth/revoke":{"post":{"operationId":"oauth/revoke","summary":"Revoke a token (RFC 7009)","tags":["oauth"],"description":"Revokes a refresh token (and, for a client the End-User had to allow, the grant behind it). Access tokens are short-lived and not revocable; the response is 200 either way, also for an unknown token. The client authenticates as at the token endpoint: `client_secret_basic`, `client_secret_post`, or just `client_id` for a public client.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string"},"token_type_hint":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_client` — The `client_id` in the request does not name a client of this account.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_client","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/saml/metadata/{client_id}":{"get":{"operationId":"saml/metadata","summary":"SAML IdP metadata","tags":["saml"],"description":"This tenant's SAML 2.0 IdP metadata for one client: entity ID, single sign-on URL and signing certificates. Paste it, or its URL, into the Service Provider.","parameters":[{"schema":{"type":"string","minLength":1},"in":"path","name":"client_id","required":true}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`saml_not_enabled` — This client does not have SAML enabled.\n\n`client_not_found` — No client with that `client_id`.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["saml_not_enabled","client_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/saml/sso/{client_id}":{"get":{"operationId":"saml/sso","summary":"SAML single sign-on","tags":["saml"],"description":"Single sign-on service (HTTP-Redirect and HTTP-POST bindings). With a SAMLRequest it answers the Service Provider; without one it starts an IdP-initiated login. The signed response is posted to the client's configured ACS URL.","parameters":[{"schema":{"type":"string"},"in":"query","name":"SAMLRequest","required":false},{"schema":{"type":"string"},"in":"query","name":"RelayState","required":false},{"schema":{"type":"string"},"in":"query","name":"SigAlg","required":false},{"schema":{"type":"string"},"in":"query","name":"Signature","required":false},{"schema":{"type":"string","minLength":1},"in":"path","name":"client_id","required":true}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_saml_request` — The SAMLRequest is missing, too large, not well-formed or not a SAML 2.0 AuthnRequest.\n\n`saml_sp_mismatch` — The AuthnRequest does not match the client's SAML configuration: its Issuer is not the configured entity_id, or it asks for another Assertion Consumer Service URL or binding.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_saml_request","saml_sp_mismatch","validation_error"]}}}]}}}},"404":{"description":"`saml_not_enabled` — This client does not have SAML enabled.\n\n`client_not_found` — No client with that `client_id`.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["saml_not_enabled","client_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}},"post":{"operationId":"saml/sso_post","summary":"SAML single sign-on","tags":["saml"],"description":"Single sign-on service (HTTP-Redirect and HTTP-POST bindings). With a SAMLRequest it answers the Service Provider; without one it starts an IdP-initiated login. The signed response is posted to the client's configured ACS URL.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"SAMLRequest":{"type":"string"},"RelayState":{"type":"string"},"SigAlg":{"type":"string"},"Signature":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string","minLength":1},"in":"path","name":"client_id","required":true}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_saml_request` — The SAMLRequest is missing, too large, not well-formed or not a SAML 2.0 AuthnRequest.\n\n`saml_sp_mismatch` — The AuthnRequest does not match the client's SAML configuration: its Issuer is not the configured entity_id, or it asks for another Assertion Consumer Service URL or binding.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_saml_request","saml_sp_mismatch","validation_error"]}}}]}}}},"404":{"description":"`saml_not_enabled` — This client does not have SAML enabled.\n\n`client_not_found` — No client with that `client_id`.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["saml_not_enabled","client_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/saml/callback":{"get":{"operationId":"saml/callback","summary":"SAML bridge callback","tags":["saml"],"description":"Internal: where the login flow returns to finish a SAML sign-on. Not for direct use.","parameters":[{"schema":{"type":"string","minLength":1},"in":"query","name":"state","required":true},{"schema":{"type":"string"},"in":"query","name":"code","required":false},{"schema":{"type":"string"},"in":"query","name":"error","required":false},{"schema":{"type":"string"},"in":"query","name":"error_description","required":false},{"schema":{"type":"string"},"in":"query","name":"iss","required":false}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_state","validation_error"]}}}]}}}},"404":{"description":"`saml_not_enabled` — This client does not have SAML enabled.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["saml_not_enabled","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/usernamepassword/login":{"post":{"operationId":"usernamepassword/login","summary":"Username + password login","tags":["usernamepassword"],"description":"Authenticates a user with username/password credentials against the database connection and renders an auto-submitting form that posts the resulting token to `/login/callback`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["username","password","client_id"],"properties":{"username":{"type":"string"},"password":{"type":"string"},"state":{"type":"string","description":"An opaque value the applications adds to the initial request that the authorization server includes when redirecting the back to the application. This value must be used by the application to prevent CSRF attacks."},"response_type":{"type":"string"},"code_challenge":{"type":"string","description":"PKCE challenge, without a `state` only."},"code_challenge_method":{"type":"string","description":"Only `S256` is supported."},"redirect_to":{"type":"string","description":"Same-origin path to return the user to after authentication, instead of the OAuth client redirect. Used by first-party interactive flows (e.g. device-code confirm). Non same-origin values are ignored."},"remember_me":{"type":"boolean","description":"The \"Remember me on this device\" checkbox. Honoured only when the tenant shows one (`login_methods.remember_me: \"optional\"`): `true` keeps the session for `remember_me_days`, `false` ends it when the browser closes."},"client_id":{"type":"string"},"connection":{"type":"string","description":"Deprecated. Use connection_id"},"connection_id":{"type":"string","description":"Which oauth connection to use. Leave empty to use default connection"},"scope":{"type":"string"},"redirect_uri":{"type":"string","description":"The URL to which Faable will redirect the browser after authorization has been granted by the user."},"nonce":{"type":"string","description":"OIDC §3.1.2.1 — string value used to associate a Client session with an ID Token, to mitigate replay attacks. If passed at /authorize, the same value MUST appear in the issued id_token."},"max_age":{"type":"number","description":"OIDC §3.1.2.1 — maximum allowable elapsed time in seconds since the last End-User authentication. If exceeded, the OP re-authenticates and `auth_time` is REQUIRED in the resulting id_token.","minimum":0},"prompt":{"type":"string"},"login_hint":{"type":"string","description":"OIDC §3.1.2.1 — hint about the identifier (email, username) the End-User will sign in with. Pre-fills the hosted login screen and is forwarded to upstream identity providers."},"id_token_hint":{"type":"string","description":"OIDC §3.1.2.1 — an id_token this provider issued earlier for the user the client believes is signed in. Verified (signature, not expiry). With `prompt=none`, a session for a different user answers `login_required`; otherwise it forces a fresh sign-in."},"acr_values":{"type":"string","description":"OIDC §3.1.2.1 — space-separated Authentication Context Class References, in order of preference. `urn:faable:loa:2` asks for a second factor: a session that only has one is challenged (or, with `prompt=none`, answers `interaction_required`)."},"ui_locales":{"type":"string","description":"OIDC §3.1.2.1 — preferred languages for the hosted screens, space-separated BCP47 tags. Forwarded to upstream identity providers."},"audience":{"type":"string","description":"Auth0-compatible alias for `resource` (RFC 8707). When both are sent they must match. Identifier of the Api the client wants to access; persisted in the auth state so the token issued at /oauth/token targets the matching Api resource."},"resource":{"type":"string","description":"RFC 8707 Resource Indicator. MUST be an absolute URI without fragment. When matched against a registered Api in the tenant the issued access_token gets `aud = <api.identifier>`."}},"description":"UsernamepasswordLoginBody","additionalProperties":false}}},"description":"UsernamepasswordLoginBody"},"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_code` — The verification code is wrong, expired or already used.\n\n`invalid_connection` — The connection does not exist, is disabled, or is not of the type this flow needs.\n\n`invalid_credentials` — The email/username or password is incorrect.\n\n`invalid_request` — A required OAuth parameter is missing or two of them are incompatible. `message` says which.\n\n`oauth21_required` — The tenant enforces OAuth 2.1 (`oauth21_enabled`) and the request is one OAuth 2.1 removed: a `response_type` other than `code`, an authorization request without PKCE (`code_challenge` with `S256`), or the password grant. `message` says which. Update the application (with `@faable/auth-js`, version 3.1.0 or later) or turn `oauth21_enabled` off for the tenant.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_code","invalid_connection","invalid_credentials","invalid_request","oauth21_required","validation_error"]}}}]}}}},"401":{"description":"`login_denied` — A post-login Action denied the sign-in. `message` carries the reason it gave. On token grants it is a 403 with `error: invalid_grant`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["login_denied"]}}}]}}}},"403":{"description":"`user_suspended` — The user is suspended and cannot sign in or be modified.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["user_suspended"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/login/callback":{"post":{"operationId":"usernamepassword/loginCallback","summary":"Username + password login callback","tags":["usernamepassword"],"description":"Completes the username/password login flow. The token issued by `/usernamepassword/login` is posted back here together with the serialized state (`wctx`) so the server can redirect the user agent to the original client `redirect_uri`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["wa","wresult","wctx"],"properties":{"wa":{"type":"string","description":"is always wsignin1.0"},"wresult":{"type":"string","description":"JWT"},"wctx":{"type":"string","description":"Serialized JSON with context"}}}}}},"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_request` — A required OAuth parameter is missing or two of them are incompatible. `message` says which.\n\n`invalid_state` — The `state` is missing, expired, or does not describe a resumable step.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_request","invalid_state","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/dbconnections/change_password":{"post":{"operationId":"change_password","summary":"Change password","tags":["dbconnection"],"description":"Change password","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string"},"channel":{"anyOf":[{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]}],"description":"How to deliver the reset: `email` (link), `sms` / `whatsapp` (a 6-digit code to the verified phone on file). Silently falls back to the tenant default when the channel is not available for this user — the response never says which channels an account has."}}}}}},"responses":{"200":{"description":"Default Response"},"400":{"description":"`invalid_client` — The `client_id` in the request does not name a client of this account.\n\n`invalid_connection` — The connection does not exist, is disabled, or is not of the type this flow needs.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_client","invalid_connection","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/reset-verify":{"get":{"operationId":"change_password_verify","summary":"Change password verify","tags":["dbconnection"],"description":"Change password verify","parameters":[{"schema":{"type":"string"},"in":"query","name":"ticket","required":true}],"responses":{"200":{"description":"Default Response"},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/reset-change":{"post":{"operationId":"change_password_change","summary":"Reset password change","tags":["dbconnection"],"description":"Reset password change","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state","new_password"],"properties":{"state":{"type":"string"},"new_password":{"type":"string","minLength":1}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["ok"]}}}}}},"400":{"description":"`invalid_code` — The verification code is wrong, expired or already used.\n\n`invalid_connection` — The connection does not exist, is disabled, or is not of the type this flow needs.\n\n`invalid_ticket` — The ticket does not exist or is not the kind this endpoint accepts.\n\n`password_too_weak` — The password does not meet the connection policy. `message` lists each unmet rule.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_code","invalid_connection","invalid_ticket","password_too_weak","validation_error"]}}}]}}}},"404":{"description":"`credential_not_found` — The user has no password credential on this connection.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["credential_not_found","account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/reset-code/verify":{"post":{"operationId":"reset_code_verify","summary":"Redeem a password-reset code sent by SMS/WhatsApp","tags":["dbconnection"],"description":"Second entry point to the new-password screen, for a reset delivered as a 6-digit code instead of a link. Answers the same `redirect_url` that the emailed link would land on. Five wrong codes consume the ticket; the error is always `invalid_code` and reveals nothing about the account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","code"],"properties":{"email":{"type":"string"},"code":{"type":"string","minLength":4,"maxLength":12}},"additionalProperties":false}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["redirect_url"],"properties":{"redirect_url":{"type":"string"}}}}}},"400":{"description":"`invalid_code` — The verification code is wrong, expired or already used.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["invalid_code","validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/dbconnections/recovery_options":{"post":{"operationId":"recovery_options","summary":"Recovery channels to offer for an identifier","tags":["dbconnection"],"description":"For the forgot-password screen. With the tenant's `visible` list empty (the default) it answers from configuration alone and never looks the account up. Otherwise it returns the channels the user can pick, with masked destinations.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string"}},"additionalProperties":false}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","required":["picker","default","channels"],"properties":{"picker":{"type":"boolean","description":"Whether the screen should offer a choice. False when the tenant keeps `visible` empty — then nothing here depends on the account existing."},"default":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"channels":{"type":"array","items":{"type":"object","required":["channel"],"properties":{"channel":{"anyOf":[{"type":"string","enum":["email"]},{"type":"string","enum":["sms"]},{"type":"string","enum":["whatsapp"]},{"type":"string","enum":["factor"]}]},"destination_masked":{"type":"string"}}}}}}}}},"400":{"description":"`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["validation_error"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/dbconnections/signup":{"post":{"operationId":"signup","summary":"Self-service signup","tags":["dbconnection"],"description":"Creates a new user and database credential in a single call against the tenant database connection. The identifier follows the connection `login_identifier` (email, username, or either); the password is optional, and a user created without one sets it through the password reset email. Public and account-scoped (no management token). Gated per-connection by `disable_signup`. The user is created with `email_verified: false`; the account notification settings (`verify_email_auto_send`, `welcome_email_enabled`) drive any follow-up emails. Does not establish a session — the client logs in afterwards.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"Email login identifier. Required unless the connection signs in by username, and whenever `password` is omitted."},"username":{"type":"string","description":"Username login identifier, stored as typed (case-sensitive). Required on a connection whose `login_identifier` is `username`; rejected on an `email` one. No `@` or whitespace."},"password":{"type":"string","description":"Plaintext password (validated against the connection policy, hashed on write). Omit it to create the user without one: they set it through the password reset email."},"name":{"type":"string"},"given_name":{"type":"string"},"family_name":{"type":"string"},"user_metadata":{"type":"object","additionalProperties":{}},"connection":{"type":"string","description":"Optional connection_name to disambiguate when the tenant has more than one database connection. Defaults to the tenant database connection."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Default Response"},"400":{"description":"`bad_request` — The request is malformed. `message` says what is wrong.\n\n`invalid_connection` — The connection does not exist, is disabled, or is not of the type this flow needs.\n\n`invalid_username` — The username is empty, or contains an `@` or whitespace. `message` says which.\n\n`password_too_weak` — The password does not meet the connection policy. `message` lists each unmet rule.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["bad_request","invalid_connection","invalid_username","password_too_weak","validation_error"]}}}]}}}},"403":{"description":"`signup_denied` — A pre-signup Action or an active block denied the sign-up. `message` carries the reason.\n\n`signup_disabled` — Self-service sign-up is disabled on this connection.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["signup_denied","signup_disabled"]}}}]}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"409":{"description":"`email_taken` — Another user in this account already uses that email.\n\n`username_taken` — Another user of this database connection already uses that username.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["email_taken","username_taken"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}},"/version":{"get":{"operationId":"getVersion","summary":"Get the running service version and git commit","tags":["system"],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string"},"commit":{"type":"string"}}}}}},"404":{"description":"`account_not_found` — No Auth Account matches the request (domain, header or token).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["account_not_found"]}}}]}}}},"429":{"description":"`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["too_many_requests"]}}}]}}}},"500":{"description":"`internal_error` — Unexpected server error. Retry later; the request id is logged.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error_code":{"type":"string","enum":["internal_error"]}}}]}}}}}}}},"servers":[{"url":"https://faable.auth.faable.link","description":"Faable (account_6475c88abdb00153b3494b8e)"}],"tags":[{"name":"user","description":"👨🏻‍💻 Manage Users"},{"name":"passwordless","description":"🪄 Passwordless authorization","externalDocs":{"url":"https://faable.com/docs/auth/passwordless"}},{"name":"session","description":"🪄 Current session endpoints"},{"name":"mfa","description":"Second factor: enrol authenticator apps and passkeys, answer challenges, step up a session, recover with a factor. Session-authenticated; used by the hosted screens.","externalDocs":{"url":"https://faable.com/docs/auth/mfa"}},{"name":"client","description":"🌏 Manage OAuth Clients","externalDocs":{"url":"https://faable.com/docs/auth/clients"}},{"name":"connect","description":"«Connect with Faable»: a one-time, PKCE-bound handoff of an OAuth client’s credentials to a third-party site (the WordPress plugin), so nobody copies a secret by hand."},{"name":"oauth","description":"OAuth Endpoints","externalDocs":{"url":"https://faable.com/docs/auth/oauth-flows/authorization-code"}},{"name":"connection","description":"Social connections and Authorization Servers","externalDocs":{"url":"https://faable.com/docs/auth/connections"}},{"name":"identity","description":"Identities fetched from Connections like Google, Slack, Facebook, etc."},{"name":"role","description":"Account-wide roles. A Role has a name (e.g. `admin`, `editor`) and a set of Api permissions. Assigned to a user at the account level (`POST /role/:role_id/users` or `POST /user/:user_id/roles`), it puts those permissions in the user's tokens for every Api with `rbac` enabled. The same Roles can be held inside a Team, where they are names only (the `roles` claim) and grant no permissions."},{"name":"rolemember","description":"Grants of a Role to a User at the account level: what gives a user Api permissions. Each RoleMember binds one (role, user) pair. From the role side, `GET`, `POST` and `DELETE /role/:role_id/users`; from the user side, `GET`, `POST` and `DELETE /user/:user_id/roles`, and `GET /user/:user_id/permissions` for the resulting permissions."},{"name":"team","description":"Groups of Users inside an Account. Teams scope membership and per-team role assignments — a User can belong to multiple Teams and hold different Roles in each. Members are managed via the `/team/:team_id/member` sub-resource."},{"name":"api","description":"Resource Servers protected by Faable Auth. Each Api declares an `identifier` (audience URL clients pass as `audience=` when requesting a token), the `permissions` it exposes, and its token issuance policy (`token_dialect`, `token_lifetime`, `enforce_policies`). Tokens issued with `audience=<identifier>` carry that identifier in the `aud` claim and, when `token_dialect=access_token_authz`, a filtered `permissions` claim.","externalDocs":{"url":"https://faable.com/docs/auth/apis"}}]}