SSO & SCIM

Enterprise SAML 2.0 / OIDC single sign-on, the authenticated SSO config admin surface, and SCIM 2.0 user provisioning.

30 endpoints. Generated from the OpenAPI 3.1 specification.

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

NameInTypeRequiredDescription
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

StatusDescription
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

NameInTypeRequiredDescription
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

StatusDescription
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

StatusDescription
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

NameInTypeRequiredDescription
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

StatusDescription
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.
GET /v1/auth/sso/saml/metadata/{configId}

Serve SAML SP metadata XML

Returns the SP metadata XML (`application/samlmetadata+xml`) for the given SAML configuration. IdP administrators import this document to configure the trust relationship. The `org_id` query parameter is required to scope the config lookup and prevent enumeration. Responds with `404 SSO_CONFIG_NOT_FOUND` for any failure (including unknown org, wrong config, or a non-SAML config type) to prevent enumeration. Rate-limited per IP and per tenant config. Cached 1 hour (`Cache-Control: public, max-age=3600`). PUBLIC — no credentials required.

Auth

Parameters

NameInTypeRequiredDescription
configId path string<uuid> yes UUID of the SAML SSO configuration.
org_id query string<uuid> yes Organization UUID that owns the SAML configuration (required to scope the lookup).

Responses

StatusDescription
200 SAML SP metadata XML document.
404 `SSO_CONFIG_NOT_FOUND` — config not found, org_id missing, or any other failure (responses are uniform to prevent enumeration).
429 Rate limit exceeded (per-IP or per-config tenant limit).
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

StatusDescription
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

StatusDescription
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
FieldTypeDescription
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

StatusDescription
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

StatusDescription
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

StatusDescription
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

NameInTypeRequiredDescription
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

StatusDescription
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

FieldTypeRequiredDescription
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

StatusDescription
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

NameInTypeRequiredDescription
id path string<uuid> yes User UUID (UUIDv4/v7).

Responses

StatusDescription
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

NameInTypeRequiredDescription
id path string<uuid> yes User UUID.

Request Body

FieldTypeRequiredDescription
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

StatusDescription
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

NameInTypeRequiredDescription
id path string<uuid> yes User UUID.

Request Body

FieldTypeRequiredDescription
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

StatusDescription
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

NameInTypeRequiredDescription
id path string<uuid> yes User UUID.

Responses

StatusDescription
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

NameInTypeRequiredDescription
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

StatusDescription
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

FieldTypeRequiredDescription
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

StatusDescription
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
FieldTypeDescription
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

NameInTypeRequiredDescription
id path string<uuid> yes UUID of the SSO configuration to update.

Request Body

FieldTypeRequiredDescription
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

StatusDescription
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
FieldTypeDescription
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

NameInTypeRequiredDescription
id path string<uuid> yes UUID of the SSO configuration to delete.

Responses

StatusDescription
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
FieldTypeDescription
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

NameInTypeRequiredDescription
id path string<uuid> yes UUID of the SSO configuration to test.

Request Body

JSON object.

Responses

StatusDescription
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
FieldTypeDescription
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

FieldTypeRequiredDescription
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

StatusDescription
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
FieldTypeDescription
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

FieldTypeRequiredDescription
metadata_url string<uri> yes URL of the IdP SAML metadata XML document. Must be a public HTTPS URL (SSRF blocklist enforced).

Responses

StatusDescription
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
FieldTypeDescription
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

NameInTypeRequiredDescription
page query integer no Page number (1-based, default 1).
page_size query integer no Records per page (1-100, default 25).

Responses

StatusDescription
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
FieldTypeDescription
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

FieldTypeRequiredDescription
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

StatusDescription
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
FieldTypeDescription
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

NameInTypeRequiredDescription
domain path string yes The domain name to remove (URL-encoded if it contains special characters, max 253 chars).

Responses

StatusDescription
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
FieldTypeDescription
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

NameInTypeRequiredDescription
domain path string yes The domain whose token to rotate (max 253 chars).

Request Body

JSON object.

Responses

StatusDescription
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
FieldTypeDescription
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

NameInTypeRequiredDescription
domain path string yes The domain to verify (max 253 chars, must match an existing domain mapping).

Request Body

JSON object.

Responses

StatusDescription
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
FieldTypeDescription
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

NameInTypeRequiredDescription
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

StatusDescription
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

NameInTypeRequiredDescription
id path string<uuid> yes UUID of the SSO identity record to unlink.

Responses

StatusDescription
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
FieldTypeDescription
deleted boolean (enum) true