Enterprise SAML 2.0 / OIDC single sign-on, the authenticated SSO config admin surface, and SCIM 2.0 user provisioning.
GET /v1/auth/sso/oidc/authorize
Initiate OIDC authorization
Begins an OIDC authorization-code flow with PKCE (S256). Performs OIDC discovery on the issuer, generates state/nonce/PKCE artifacts, stores HMAC-bound flow state (TTL 10 min), and redirects the user-agent to the IdP authorization endpoint. Rate-limited per client IP (20 req/60 s). On success the response is a `302 Found` redirect to the IdP — NOT a JSON body. Error conditions that are detectable before the redirect return a JSON error envelope. PUBLIC — no credentials required.
Auth
Parameters
| Name | In | Type | Required | Description |
config_id | query | string<uuid> | yes | UUID of the SSO configuration to use (from POST /v1/auth/lookup). |
org_id | query | string<uuid> | yes | Organization UUID that owns the SSO configuration. |
redirect_uri | query | string<uri> | no | Optional post-login redirect URI within the allowed app origin. Silently ignored when it points to a disallowed domain (open-redirect prevention). |
Responses
| Status | Description |
302 | Redirect to the IdP authorization endpoint. |
400 | `VALIDATION_ERROR` — a query parameter failed validation. `SSO_CONFIG_INACTIVE` — the SSO configuration is disabled. |
404 | `SSO_CONFIG_NOT_FOUND` — the config_id/org_id pair does not exist. |
429 | `RATE_LIMIT_EXCEEDED` — per-IP rate limit exceeded. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
502 | `SSO_DISCOVERY_FAILED` — OIDC discovery document could not be fetched from the IdP. |
503 | `SERVICE_UNAVAILABLE` — SSO service is misconfigured or temporarily unavailable. |
GET /v1/auth/sso/oidc/callback
OIDC IdP callback
Receives the browser redirect from the IdP after the user authenticates (authorization-code + state). Validates state, HMAC integrity, PKCE, and the ID token; completes the SSO login; then sets an HttpOnly `__Secure-bb_sso_pickup` cookie (TTL 60 s, path-scoped to `/v1/auth/sso/token-exchange`) and redirects the browser to `<APP>/auth/sso/complete?flow=pickup`. On MFA-required flows, redirects to `<APP>/auth/mfa/challenge` instead. Rate-limited. PUBLIC — no credentials required. On error, redirects to `<APP>/auth/sso/error?code=...&message=...` (browser redirect, not a JSON body).
Auth
Parameters
| Name | In | Type | Required | Description |
code | query | string | no | Authorization code from the IdP. |
state | query | string | no | State parameter echoed by the IdP (bound to the stored flow state). |
error | query | string | no | OAuth 2.0 error code sent by the IdP on failure. |
error_description | query | string | no | Human-readable OAuth 2.0 error description from the IdP. |
Responses
| Status | Description |
302 | Redirect to the frontend SSO completion page (success), MFA challenge page, or the SSO error page (failure). Error details are in `code` and `message` query parameters on the error redirect. The specific `error.code` values that may appear are: `SSO_CALLBACK_ERROR`, `SSO_STATE_INVALID`, `SSO_STATE_EXPIRED`, `SSO_TOKEN_VALIDATION_FAILED`, `SSO_CONFIG_NOT_FOUND`, `SSO_DISCOVERY_FAILED`, `SSO_LOGIN_FAILED`, `INTERNAL_ERROR`, `SERVICE_UNAVAILABLE`. |
POST /v1/auth/sso/saml/acs
SAML Assertion Consumer Service (ACS)
Receives the SAML Response from the IdP via HTTP-POST binding (`application/x-www-form-urlencoded` body with `SAMLResponse` + `RelayState`). Validates the response: XML signature, assertion expiry, audience, replay protection (per-org assertion ID table), and optional MFA context class. On success, sets the `__Secure-bb_sso_pickup` HttpOnly cookie and redirects to `<APP>/auth/sso/complete?flow=pickup`. On MFA-required flows, redirects to `<APP>/auth/mfa/challenge`. On error, redirects to `<APP>/auth/sso/error`. Rate-limited. Body size capped at 512 KB. PUBLIC — no credentials required.
Auth
Responses
| Status | Description |
302 | Redirect to the SSO completion page, MFA challenge page, or error page. Error codes on the error redirect: `SAML_RESPONSE_INVALID`, `SSO_STATE_INVALID`, `SSO_STATE_EXPIRED`, `SAML_SIGNATURE_INVALID`, `SAML_ASSERTION_EXPIRED`, `SAML_AUDIENCE_MISMATCH`, `SAML_REPLAY_DETECTED`, `SSO_LOGIN_FAILED`, `SSO_CALLBACK_ERROR`, `SERVICE_UNAVAILABLE`. |
GET /v1/auth/sso/saml/login
Initiate SAML SP-initiated SSO
Begins a SAML 2.0 SP-initiated authentication flow. Loads the SAML configuration (decrypting the SP signing key if present), builds a signed `AuthnRequest` (HTTP-Redirect binding), stores HMAC-bound flow state (TTL 10 min), and redirects the user-agent to the IdP SSO URL. Rate-limited per client IP. PUBLIC — no credentials required.
Auth
Parameters
| Name | In | Type | Required | Description |
config_id | query | string<uuid> | yes | UUID of the SAML SSO configuration. |
org_id | query | string<uuid> | yes | Organization UUID that owns the SAML configuration. |
redirect_uri | query | string<uri> | no | Optional post-login redirect URI within the allowed app origin. |
Responses
| Status | Description |
302 | Redirect to the IdP SSO URL. |
400 | `VALIDATION_ERROR` — a query parameter failed validation. `SSO_CONFIG_NOT_FOUND` — config not found or inactive. |
429 | `RATE_LIMIT_EXCEEDED` — per-IP rate limit exceeded. |
503 | `SERVICE_UNAVAILABLE` — SSO service is temporarily unavailable. |
POST /v1/auth/sso/saml/slo
SAML Single Logout (IdP-initiated)
Receives a `SAMLRequest` (LogoutRequest) from the IdP via HTTP-POST binding. Validates the XML signature against the configured IdP certificate, checks IssueInstant freshness (-5 min .. +1 min window), enforces KV-backed replay protection (10 min TTL), looks up and revokes the target user's session(s) (by SessionIndex when present, by global opt-in fallback when not), and returns an auto-submitting HTML form that POSTs the signed `SAMLResponse` back to the IdP SLO endpoint. Rate-limited (5 req/min/IP). Body capped at 512 KB. Requires `config_id` form field (UUID) so the org can be resolved. PUBLIC — no credentials required (the SAML XML signature IS the credential).
Auth
Responses
| Status | Description |
200 | HTML auto-submit form that POSTs the SAML LogoutResponse back to the IdP. |
400 | Bad request — missing/invalid `SAMLRequest`, missing/invalid `config_id`, IssueInstant out of window, issuer mismatch, XML parse error, or signature verification failed. Response body is plain text. |
409 | The SAMLRequest ID has already been processed (replay protection). Response body is plain text. |
413 | Request body exceeds 512 KB limit. Response body is plain text. |
429 | Per-IP rate limit exceeded (5 req/min). Response body is plain text. |
500 | Server error — SP signing key not configured or SLO URL missing. Response body is plain text. |
503 | SSO service temporarily unavailable. Response body is plain text. |
POST /v1/auth/sso/token-exchange
Exchange SSO one-time pickup cookie for auth tokens
Completes the SSO login by consuming the `__Secure-bb_sso_pickup` HttpOnly cookie set by the OIDC callback or SAML ACS handler. The cookie carries a single-use 64-hex-char exchange code (TTL 60 s). The endpoint requires an exact-origin `Origin` header (CSRF protection), clears the cookie on read, and returns the access/refresh tokens in the same envelope shape as `POST /v1/auth/login`. Rate-limited per client IP. PUBLIC — no credentials required (the cookie IS the credential).
Auth
Request Body
JSON object.
Responses
| Status | Description |
200 | Tokens issued. The `isNewUser` field is intentionally omitted to prevent account enumeration. |
400 | `SSO_TOKEN_EXCHANGE_INVALID` — cookie is missing, malformed, or the code format is invalid. |
401 | `SSO_TOKEN_EXCHANGE_INVALID` — the exchange code is expired or has already been consumed. |
403 | `SSO_TOKEN_EXCHANGE_INVALID` — `Origin` header is missing, malformed, or does not match the allowed app origin. |
429 | `RATE_LIMIT_EXCEEDED` — per-IP rate limit exceeded. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
503 | `SERVICE_UNAVAILABLE` — SSO service is temporarily unavailable. |
200 response body: data fields
| Field | Type | Description |
success | boolean (enum) true | Always true. |
userId | string<uuid> | Authenticated user ID. |
email | string<email> | User email address. |
displayName | string | null | User display name. |
orgId | string<uuid> | Organization the session is scoped to. |
accessToken | string | Short-lived JWT access token. |
refreshToken | string | Long-lived opaque refresh token. |
expiresIn | integer | Access token TTL in seconds (typically 900). |
tokenType | string (enum) Bearer | Token type. |
GET /v1/scim/v2/ResourceTypes
SCIM ResourceTypes (discovery)
Returns the list of SCIM resource types supported by the platform. Only the `User` resource type is advertised (Group provisioning is not implemented). PUBLIC — no credentials required. Rate-limited: 60 req/min/IP.
Auth
Responses
| Status | Description |
200 | SCIM ListResponse containing the User resource type. |
429 | Per-IP rate limit exceeded. |
GET /v1/scim/v2/Schemas
SCIM Schemas (discovery)
Returns the SCIM schema definitions for the User resource type (RFC 7643 subset: `userName`, `externalId`, `displayName`, `active`, `name.givenName`, `name.familyName`, `name.formatted`, `emails`). PUBLIC — no credentials required. Rate-limited: 60 req/min/IP.
Auth
Responses
| Status | Description |
200 | SCIM ListResponse containing the User schema definition. |
429 | Per-IP rate limit exceeded. |
GET /v1/scim/v2/ServiceProviderConfig
SCIM ServiceProviderConfig (discovery)
Returns the SCIM 2.0 ServiceProviderConfig document describing platform capabilities: filter support (`maxResults=200`), patch support, no bulk/sort/changePassword. Used by IdP onboarding tooling to discover capabilities. PUBLIC — no credentials required. Rate-limited: 60 req/min/IP.
Auth
Responses
| Status | Description |
200 | SCIM ServiceProviderConfig document. |
429 | Per-IP rate limit exceeded (60 req/min). |
GET /v1/scim/v2/Users
SCIM: List / search users
Returns a SCIM ListResponse of users in the bound organization. Supports a narrow filter grammar: `userName eq "<email>"`, `externalId eq "<id>"`, `id eq "<uuid>"`, and `<clause> and <clause>`. Filters outside this grammar are rejected with `invalidFilter`. `startIndex` is 1-based (max 10 000). `count` defaults to 100 and is capped at 200. Requires `scimToken` bearer.
Auth SCIM bearer token
Parameters
| Name | In | Type | Required | Description |
filter | query | string | no | SCIM filter expression (narrow grammar: `userName eq`, `externalId eq`, `id eq`, and AND of two). Anything outside this grammar is rejected as `invalidFilter`. |
startIndex | query | integer | no | 1-based start index for pagination (max 10 000, default 1). |
count | query | integer | no | Number of results to return (default 100, max 200). |
Responses
| Status | Description |
200 | SCIM ListResponse. |
400 | SCIM error `invalidFilter` — filter is outside the supported grammar. `tooMany` — `startIndex` exceeds 10 000. |
401 | SCIM bearer token invalid or expired. |
429 | SCIM rate limit exceeded. |
500 | Server error. |
POST /v1/scim/v2/Users
SCIM: Create user
Provisions a new user in the organization bound to the SCIM bearer token. `userName` (email) is required and lowercased. The primary email (from `emails[primary=true].value` or `userName`) must pass RFC 5321 format validation (rejects control bytes, BIDI overrides, malformed structure). `active` defaults to `true`. All write operations are audit-logged. Returns `201 Created` with `Location` header. Requires a valid `scimToken` bearer token.
Auth SCIM bearer token
Request Body
| Field | Type | Required | Description |
schemas | array of string | no | SCIM schema URNs (e.g., ["urn:ietf:params:scim:schemas:core:2.0:User"]). |
userName | string | yes | User email address. Lowercased and trimmed before storage. Immutable after creation. |
externalId | string | no | IdP-assigned external identifier. |
displayName | string | no | User display name. |
active | boolean | no | Whether the user is active (default true). |
name | object | no | |
emails | array of object | no | Email addresses. The primary=true entry is used as the primary_email; falls back to userName. Must pass RFC 5321 validation. |
Responses
| Status | Description |
201 | User created. `Location` header contains the canonical URL. |
400 | SCIM error `invalidSyntax` — body is not valid JSON. `invalidValue` — `userName` missing or primary email fails RFC 5321 validation. |
401 | SCIM bearer token missing, malformed, revoked, or expired (`SCIM_UNAUTHORIZED`). |
409 | SCIM error `uniqueness` — a user with this `userName` or `externalId` already exists in the organization. |
429 | SCIM rate limit exceeded (1000 req/min per token). |
500 | Server error. |
GET /v1/scim/v2/Users/{id}
SCIM: Get user
Returns the SCIM User resource for the given user UUID. Returns `404` (SCIM error envelope) for non-UUID IDs or unknown users. Requires `scimToken` bearer.
Auth SCIM bearer token
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | User UUID (UUIDv4/v7). |
Responses
| Status | Description |
200 | The SCIM User resource. |
401 | SCIM bearer token invalid. |
404 | User not found (also returned for non-UUID `id` values). |
429 | SCIM rate limit exceeded. |
500 | Server error. |
PUT /v1/scim/v2/Users/{id}
SCIM: Replace user (full update)
Full-replace of a SCIM User resource. `userName` is immutable — it cannot be changed via PUT. Updatable fields: `displayName`, `name.givenName`, `name.familyName`, `active`. Setting `active=false` triggers session revocation inside the SP. The response reflects what the SP actually persisted (re-fetched after update). Requires `scimToken` bearer.
Auth SCIM bearer token
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | User UUID. |
Request Body
| Field | Type | Required | Description |
schemas | array of string | no | |
displayName | string | no | |
active | boolean | no | Setting false triggers session revocation. |
name | object | no | |
emails | array of object | no | |
Responses
| Status | Description |
200 | Updated SCIM User resource. |
400 | SCIM error `invalidSyntax` — body is not valid JSON. |
401 | SCIM bearer token invalid. |
404 | User not found. |
429 | SCIM rate limit exceeded. |
500 | Server error. |
PATCH /v1/scim/v2/Users/{id}
SCIM: Patch user (partial update)
Applies a SCIM PATCH (`Operations` array) to the user. Only `op: "replace"` is supported. Supported paths: `active`, `displayName`, `name.givenName`, `name.familyName`, `emails[primary eq true].value` (no-op — `userName` is immutable). Maximum 100 operations per request. Setting `active=false` triggers session revocation. Audit-logged. Requires `scimToken` bearer.
Auth SCIM bearer token
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | User UUID. |
Request Body
| Field | Type | Required | Description |
schemas | array of string | no | |
Operations | array of object | yes | Array of PATCH operations. Only op: replace is supported. Supported paths: active, displayName, name.givenName, name.familyName, emails[primary eq true].value (no-op). |
Responses
| Status | Description |
200 | Updated SCIM User resource (re-fetched from DB). |
400 | SCIM error `invalidSyntax` — body not valid JSON or `Operations` is empty/not an array. `tooMany` — more than 100 operations. `invalidPath` — unsupported PATCH path. `invalidValue` — invalid field value. `mutability` — attempt to mutate an immutable field (`userName`). |
401 | SCIM bearer token invalid. |
404 | User not found. |
429 | SCIM rate limit exceeded. |
500 | Server error. |
DELETE /v1/scim/v2/Users/{id}
SCIM: Deprovision user
Removes the user from the organization and revokes all their active sessions. Returns `204 No Content` with no body on success, per RFC 7644. Audit-logged. Requires `scimToken` bearer.
Auth SCIM bearer token
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | User UUID. |
Responses
| Status | Description |
204 | User deprovisioned. No response body. |
401 | SCIM bearer token invalid. |
404 | User not found. |
429 | SCIM rate limit exceeded. |
500 | Server error. |
GET /v1/sso/configs
List SSO configurations
Returns the SSO configurations for the caller's organization, with optional filtering by `auth_method` and `is_active`. Paginated (default page 1, page_size 25, max 100). Provider config fields are redacted (secrets and key references are replaced with presence flags). Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
auth_method | query | string (enum) | no | Filter by SSO protocol (`saml` or `oidc`). |
is_active | query | string (enum) | no | Filter by active status (`true` or `false` as strings). |
page | query | integer | no | Page number (1-based, default 1). |
page_size | query | integer | no | Records per page (1-100, default 25). |
Responses
| Status | Description |
200 | List of SSO configurations (secrets redacted). |
400 | 400 Bad Request — a request parameter, body field, or the JSON body itself failed validation. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
POST /v1/sso/configs
Create SSO configuration
Creates a new SAML or OIDC SSO configuration for the caller's organization. `provider_config` is an object whose keys must be from the platform allowlist (unknown keys are rejected with `VALIDATION_ERROR` naming the offending key). For OIDC, the `issuer` URL is validated against the SSRF blocklist. For SAML, the `sso_url` is similarly validated. Per-org quota: maximum 50 SSO configs. Secrets (e.g., `client_secret_ref`, `sp_signing_key_ref`) must be pre-encrypted references — raw secrets are never accepted. Returns the created config with sensitive fields redacted. Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
auth_method | string (enum) samloidc | yes | SSO protocol. |
provider_config | object | yes | Protocol-specific configuration object. Only allowlisted keys are accepted (unknown keys cause a 400). Sensitive values (e.g., `client_secret_ref`, `sp_signing_key_ref`) must be pre-encrypted references. |
is_active | boolean | no | Whether to enable the config immediately (default true). |
is_default | boolean | no | Whether to set this as the org default (default false). |
Responses
| Status | Description |
201 | SSO configuration created. Provider config is returned with secrets redacted. |
400 | `VALIDATION_ERROR` — a field failed validation, unknown `provider_config` keys, SSRF-blocked URL, or `BILLING_PLAN_LIMIT_REACHED` — 50-config org quota exceeded. `INVALID_JSON` — body is not valid JSON. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | `PERMISSION_DENIED` or `FORBIDDEN` — missing `identity.manage` permission. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
201 response body: data fields
| Field | Type | Description |
id | string<uuid> | Configuration ID. |
org_id | string<uuid> | Owning organization. |
auth_method | string (enum) samloidc | SSO protocol. |
is_active | boolean | Whether the configuration is enabled for login. |
is_default | boolean | Whether this is the default SSO config for the org. |
provider_config | object | Provider configuration with secrets redacted. Non-sensitive fields are returned; `has_client_secret` and `has_sp_signing_key` flags indicate secret presence. |
created_at | string<date-time> | |
updated_at | string<date-time> | |
PUT /v1/sso/configs/{id}
Update SSO configuration
Updates an existing SSO configuration. All fields are optional; supply only the fields to change. Unknown `provider_config` keys are rejected. SSRF-blocked URLs are rejected. When `is_active` is set to `false`, all sessions authenticated via this config are force-revoked (session cascade); if revocation fails the update returns `500` and the caller should retry. Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | UUID of the SSO configuration to update. |
Request Body
| Field | Type | Required | Description |
auth_method | string (enum) samloidc | no | New SSO protocol. |
provider_config | object | no | Updated provider configuration. Only allowlisted keys are accepted. |
is_active | boolean | no | Enable or disable this config. Setting false force-revokes all active sessions. |
is_default | boolean | no | Set as org default. |
Responses
| Status | Description |
200 | Updated SSO configuration (secrets redacted). |
400 | `VALIDATION_ERROR` — invalid UUID path param, unknown `provider_config` key, SSRF-blocked URL, or body parse failure. `INVALID_JSON` — body is not valid JSON. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | `SSO_CONFIG_NOT_FOUND` — the configuration does not exist or belongs to another org. |
500 | `INTERNAL_ERROR` — includes the case where the config was disabled but session revocation failed (safe to retry). |
200 response body: data fields
| Field | Type | Description |
id | string<uuid> | Configuration ID. |
org_id | string<uuid> | Owning organization. |
auth_method | string (enum) samloidc | SSO protocol. |
is_active | boolean | Whether the configuration is enabled for login. |
is_default | boolean | Whether this is the default SSO config for the org. |
provider_config | object | Provider configuration with secrets redacted. Non-sensitive fields are returned; `has_client_secret` and `has_sp_signing_key` flags indicate secret presence. |
created_at | string<date-time> | |
updated_at | string<date-time> | |
DELETE /v1/sso/configs/{id}
Delete SSO configuration
Deletes an SSO configuration. Force-revokes all sessions authenticated via this config BEFORE deleting the row (so sessions can be located by `sso_config_id`). If revocation fails the deletion is aborted and the caller receives `500` (safe to retry). Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | UUID of the SSO configuration to delete. |
Responses
| Status | Description |
200 | Configuration deleted. `revoked_sessions` is the number of sessions that were force-revoked. |
400 | `VALIDATION_ERROR` — config ID is not a valid UUID. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | `SSO_CONFIG_NOT_FOUND` — not found or belongs to another org. |
500 | `INTERNAL_ERROR` — session revocation failed; the config was NOT deleted. Retry is safe. |
200 response body: data fields
| Field | Type | Description |
deleted | boolean (enum) true | |
revoked_sessions | integer | Number of active sessions that were force-revoked before deletion. |
POST /v1/sso/configs/{id}/test
Test SSO configuration connectivity
Performs a live connectivity test against the SSO configuration: for OIDC, fetches the discovery document and checks the JWKS endpoint; for SAML, fetches and validates the metadata XML and checks certificate presence. Returns a `200` with `status: 'ok'` or `status: 'error'` in the `data` payload regardless of the underlying reachability result (test results never cause a non-2xx status). Auth and validation failures still produce non-200 responses. Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | UUID of the SSO configuration to test. |
Request Body
JSON object.
Responses
| Status | Description |
200 | Test completed. `data.status` is `ok` when connectivity checks passed, `error` when any check failed. `data.details` contains per-check results. |
400 | `VALIDATION_ERROR` — config ID is not a valid UUID. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | `SSO_CONFIG_NOT_FOUND` — configuration not found. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
status | string (enum) okerror | ok when all checks passed; error when any check failed. |
message | string | Human-readable status message (present on error). |
details | object | Per-check detail map. For OIDC: `discovery_reachable`, `issuer`, `jwks_reachable`, `jwks_status`. For SAML: `metadata_reachable`, `metadata_valid`, `idp_entity_id`, `certificate_present`, `certificate_length`. |
POST /v1/sso/configs/import-oidc
Import OIDC configuration from discovery URL
Fetches the OIDC discovery document from `<discovery_url>/.well-known/openid-configuration`, validates all discovered endpoint URLs (SSRF protection + issuer-origin match per OIDC Discovery), encrypts the `client_secret` using a platform-managed key, and persists a new OIDC SSO configuration with `is_active=true`. Returns the created config with the secret redacted. Redirects are followed manually (per-hop SSRF re-check). Timeout: 10 s. Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
discovery_url | string<uri> | yes | OIDC issuer base URL. The endpoint `<discovery_url>/.well-known/openid-configuration` is fetched. Must be a public HTTPS URL (SSRF blocklist enforced). |
client_id | string | yes | OAuth 2.0 client ID registered with the IdP. |
client_secret | string | yes | OAuth 2.0 client secret. Encrypted at rest before storage; never returned in API responses. |
Responses
| Status | Description |
201 | OIDC configuration created from discovery document. |
400 | `VALIDATION_ERROR` — body failed validation, SSRF-blocked URL, issuer-origin mismatch, or discovery document missing required fields. `INVALID_JSON` — body is not valid JSON. `SSO_DISCOVERY_FAILED` — discovery document is malformed. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
502 | `SSO_DISCOVERY_FAILED` — the discovery endpoint returned an error status. |
503 | `SERVICE_UNAVAILABLE` — encryption service (a platform-managed key) not configured. |
504 | `TIMEOUT` — the OIDC discovery request timed out (10 s). |
201 response body: data fields
| Field | Type | Description |
id | string<uuid> | Configuration ID. |
org_id | string<uuid> | Owning organization. |
auth_method | string (enum) samloidc | SSO protocol. |
is_active | boolean | Whether the configuration is enabled for login. |
is_default | boolean | Whether this is the default SSO config for the org. |
provider_config | object | Provider configuration with secrets redacted. Non-sensitive fields are returned; `has_client_secret` and `has_sp_signing_key` flags indicate secret presence. |
created_at | string<date-time> | |
updated_at | string<date-time> | |
POST /v1/sso/configs/import-saml
Import SAML configuration from IdP metadata URL
Fetches the IdP metadata XML from `metadata_url`, parses SSO URL / entity ID / certificate / NameID formats / SLO URL, and persists a new SAML SSO configuration. The SP entity ID and ACS URL are derived from the API base URL. Redirects followed manually (per-hop SSRF check). Timeout: 10 s. Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
metadata_url | string<uri> | yes | URL of the IdP SAML metadata XML document. Must be a public HTTPS URL (SSRF blocklist enforced). |
Responses
| Status | Description |
201 | SAML configuration created from IdP metadata. |
400 | `VALIDATION_ERROR` — body failed validation or SSRF-blocked URL. `SAML_RESPONSE_INVALID` — metadata XML is malformed. `INVALID_JSON` — body is not valid JSON. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
502 | `SSO_DISCOVERY_FAILED` — the metadata endpoint returned an error status. |
504 | `TIMEOUT` — the metadata fetch timed out (10 s). |
201 response body: data fields
| Field | Type | Description |
id | string<uuid> | Configuration ID. |
org_id | string<uuid> | Owning organization. |
auth_method | string (enum) samloidc | SSO protocol. |
is_active | boolean | Whether the configuration is enabled for login. |
is_default | boolean | Whether this is the default SSO config for the org. |
provider_config | object | Provider configuration with secrets redacted. Non-sensitive fields are returned; `has_client_secret` and `has_sp_signing_key` flags indicate secret presence. |
created_at | string<date-time> | |
updated_at | string<date-time> | |
GET /v1/sso/domains
List email domain mappings
Returns the email domain mappings for the caller's organization, paginated. The response includes `rows`, `page`, `page_size`, `total`, and `total_pages` inside `data`. Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
page | query | integer | no | Page number (1-based, default 1). |
page_size | query | integer | no | Records per page (1-100, default 25). |
Responses
| Status | Description |
200 | Paginated domain mappings. |
400 | 400 Bad Request — a request parameter, body field, or the JSON body itself failed validation. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
rows | array of object | |
page | integer | |
page_size | integer | |
total | integer | |
total_pages | integer | |
POST /v1/sso/domains
Create email domain mapping
Maps an email domain to an authentication method (and optionally an SSO config). The domain is normalized to canonical Punycode form. Public/consumer email domains (gmail.com, outlook.com, etc.) are rejected. Per-org quota: 100 domains. After creation, the domain is unverified — call `POST /v1/sso/domains/{domain}/verify` to verify via DNS TXT record. Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
domain | string | yes | Email domain to map (e.g., example.com). Normalized to Punycode. Public consumer domains (gmail.com, etc.) are rejected. |
auth_method | string (enum) samloidcpasswordgoogle_ssomagic_link | yes | Authentication method to enforce for users with this email domain. |
config_id | string<uuid> | no | Optional UUID of the associated SAML/OIDC SSO configuration. |
auto_provision | boolean | no | Whether to automatically provision new users during SSO login (default false). |
Responses
| Status | Description |
201 | Domain mapping created (unverified). Response includes `verification_token` which must be published as `_backbuild-verify.<domain>` TXT record. |
400 | `VALIDATION_ERROR` — body failed validation, domain normalization error, public email provider, or `BILLING_PLAN_LIMIT_REACHED` — 100-domain org quota exceeded. `INVALID_JSON` — body is not valid JSON. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
409 | `CONFLICT` — domain is already mapped to this organization. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
503 | `SERVICE_UNAVAILABLE` — domain quota check failed (KV unavailable). |
201 response body: data fields
| Field | Type | Description |
domain | string | Normalized domain name (Punycode). |
org_id | string<uuid> | |
auth_method | string (enum) samloidcpasswordgoogle_ssomagic_link | |
config_id | string | null | |
auto_provision | boolean | |
is_verified | boolean | Whether the domain has been verified via DNS TXT record. |
verification_token | string | null | Token to publish as `_backbuild-verify.<domain>` TXT record value (present on create response). |
verified_at | string | null | |
created_at | string<date-time> | |
DELETE /v1/sso/domains/{domain}
Delete email domain mapping
Removes an email domain mapping. The `domain` path parameter is URL-decoded and normalized to Punycode before lookup. Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
domain | path | string | yes | The domain name to remove (URL-encoded if it contains special characters, max 253 chars). |
Responses
| Status | Description |
200 | Domain mapping deleted. |
400 | `VALIDATION_ERROR` — domain is empty, exceeds 253 chars, or normalization failed. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | `NOT_FOUND` — domain mapping not found. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
deleted | boolean (enum) true | |
POST /v1/sso/domains/{domain}/regenerate-token
Rotate domain verification token
Rotates the DNS verification token for a domain mapping. Sets `is_verified = false` and clears `verified_at` (but preserves `last_verified_at` for audit history). The org must re-publish the new token as a DNS TXT record and call `POST /v1/sso/domains/{domain}/verify` again. Rate-limited: 3 rotations per (org, domain) per 24 hours (fail-closed if KV is unavailable). Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
domain | path | string | yes | The domain whose token to rotate (max 253 chars). |
Request Body
JSON object.
Responses
| Status | Description |
200 | New verification token issued. `data.verification_token` is the new token to publish as `_backbuild-verify.<domain>` TXT record. |
400 | `VALIDATION_ERROR` — domain is invalid or normalization failed. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | `NOT_FOUND` — domain mapping not found. |
429 | `RATE_LIMIT_EXCEEDED` — 3 rotations per domain per 24 hours exceeded. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
503 | `SERVICE_UNAVAILABLE` — KV rate-limiter unavailable (fail-closed). |
200 response body: data fields
| Field | Type | Description |
domain | string | |
verification_token | string | New verification token. Publish as `_backbuild-verify.<domain>` TXT record value. |
verification_started_at | string<date-time> | |
is_verified | boolean (enum) false | Always false after rotation — must re-verify. |
POST /v1/sso/domains/{domain}/verify
Verify email domain via DNS TXT record
Performs a DNS TXT record lookup (via Cloudflare DoH) for `_backbuild-verify.<domain>` and compares the observed value against the stored token. The SP persists the verification result (audit-logged). Rate-limited: 10 attempts per (org, domain) per 10 minutes. If already verified, returns success immediately without re-querying DNS. On mismatch, returns `400` with `details.record_name` and `details.record_type` for the caller's reference. Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
domain | path | string | yes | The domain to verify (max 253 chars, must match an existing domain mapping). |
Request Body
JSON object.
Responses
| Status | Description |
200 | Verification succeeded. `data.is_verified` is `true`. |
400 | `VALIDATION_ERROR` — domain is invalid, normalization failed, or DNS TXT record was not found or did not match. The `error.details` contains `record_name`, `record_type`, and a propagation hint. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | `NOT_FOUND` — domain mapping does not exist for this organization. |
429 | `RATE_LIMIT_EXCEEDED` — 10 verification attempts per (org, domain) per 10 minutes exceeded. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
503 | `SERVICE_UNAVAILABLE` — KV rate-limiter unavailable (fail-closed). |
200 response body: data fields
| Field | Type | Description |
domain | string | |
is_verified | boolean | |
verified_at | string | null | |
message | string | |
GET /v1/sso/identities
List linked SSO identities
Returns the SSO identity records (IdP subject bindings) for users in the caller's organization. Filterable by `provider` string and `user_id`. Paginated. Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
provider | query | string | no | Filter by provider identifier string (max 200 chars). |
user_id | query | string<uuid> | no | Filter by user UUID. |
page | query | integer | no | Page number (1-based, default 1). |
page_size | query | integer | no | Records per page (1-100, default 25). |
Responses
| Status | Description |
200 | Paginated list of SSO identity records. |
400 | 400 Bad Request — a request parameter, body field, or the JSON body itself failed validation. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
DELETE /v1/sso/identities/{id}
Unlink SSO identity
Removes an SSO identity record (IdP subject binding) from the organization. The user account itself is NOT deleted; only the IdP link is removed. Requires `identity.manage` permission.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
id | path | string<uuid> | yes | UUID of the SSO identity record to unlink. |
Responses
| Status | Description |
200 | Identity unlinked. |
400 | `VALIDATION_ERROR` — identity ID is not a valid UUID. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | 403 Forbidden — authenticated but lacking the required membership, role, permission, MFA step-up, or feature entitlement. |
404 | `NOT_FOUND` — identity record not found. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
deleted | boolean (enum) true | |