Quick Start: SSO & SCIM
These two are independent of the bootstrap-key/human-registration setup in Authentication — reach for them once an org wants its identity provider (Entra ID, Okta, Google, …) doing the login and/or user-provisioning work instead of local passwords and manual invites.
Single sign-on (Entra ID / OIDC)
Section titled “Single sign-on (Entra ID / OIDC)”Each organization can connect its own OIDC identity provider — Entra ID, Google, Okta, or any other OIDC-compliant IdP — so its members sign in without a password. An org admin sets this up from the dashboard’s SSO settings page (or directly via the API):
curl -X POST http://localhost:8000/orgs/{org_id}/sso \ -H "X-API-Key: change-me-to-a-strong-secret" \ -H "Content-Type: application/json" \ -d '{ "display_name": "Acme Corp", "issuer": "https://login.microsoftonline.com/{tenant-id}/v2.0", "client_id": "<app-registration-client-id>", "client_secret": "<app-registration-client-secret>", "default_role": "member", "allowed_domains": ["acme.com"] }'From Entra ID: register an application, grant it the openid/email/profile scopes, and set its redirect URI to {your-api-base-url}/auth/sso/callback.
A member then signs in either of two ways:
- From the login page — enter your email and select “Continue with SSO.” The dashboard calls
POST /auth/sso/discoverto find your org from your email’s domain (viaallowed_domains), then redirects toGET /auth/sso/{org_id}/start. - Via a direct link —
GET /auth/sso/{org_id}/startworks without the discovery step, for orgs that share the link directly.
Either way, the browser is redirected to the IdP, authenticates there, and is redirected back to GET /auth/sso/callback, which completes the login and sets the same session cookie a password login would. The first SSO login for an email that doesn’t yet have an account provisions one automatically at default_role; an email that already has a local-password account is linked to it instead (the password keeps working too) rather than creating a duplicate.
default_role can never be owner — same reasoning as invitation links: an automatically-granted role must never hand out full ownership. allowed_domains is optional; if set, no other organization’s connection can claim the same domain, and login is rejected for any email outside it.
A claimed domain also takes over the platform’s Google / Microsoft sign-in buttons, if the deployment offers them: an address at that domain choosing Continue with Google is sent into this org’s SSO connection instead. So members can’t get round the org’s identity provider with a personal account on the same address.
An SSO session satisfies the organization’s require 2FA policy on its own — the IdP owns the second factor, and TOTP enrollment is only available to password accounts.
Automatic provisioning via SCIM
Section titled “Automatic provisioning via SCIM”SSO above covers authentication — how a member logs in. SCIM 2.0 provisioning covers lifecycle — Entra ID (or any SCIM-compliant IdP) pushing users and groups into an org on its own schedule, independent of anyone actually logging in. The two are independent: an org can use either, both, or neither.
From the dashboard’s SSO page, enable SCIM to get a tenant URL and a bearer token (shown once):
curl -X POST http://localhost:8000/orgs/{org_id}/scim \ -H "X-API-Key: change-me-to-a-strong-secret" \ -H "Content-Type: application/json" \ -d '{ "default_role": "member", "group_role_mappings": [ {"pattern": "^admin", "role": "admin"} ] }'# → {"token": "..."} — copy it now, it is never shown againIn Entra ID: open the Enterprise Application → Provisioning → set mode to Automatic → Tenant URL is the base_url from GET /orgs/{org_id}/scim (e.g. https://your-api/scim/v2/{org_id}) → Secret Token is the value above. Assign users/groups to the application and Entra provisions them on its own ~40-minute cycle (or on demand via “Provision on demand”).
What happens on each side:
- Users — a SCIM
Userlinks to an existing local account by email (same linking rule as SSO) or creates one;PATCH .../Users/{id}with{"active": false}deactivates the org membership (Entra does this automatically when you unassign someone),active: truereactivates it. - Groups — a SCIM
Groupmaps 1:1 to a Team; creating, renaming, or deleting a group does the same to the Team. Assigning members to the group syncs Team membership. - Group → role mapping —
group_role_mappingsis an ordered list of{pattern, role}rules, eachpatterna regular expression matched against a user’s current SCIM group names. The first rule that matches any of them wins; if none match, the config’s owndefault_roleapplies.role(anddefault_role) can never beowner— same guardrail as SSO’sdefault_role. This mapping is a deliberate, guarded exception to Team’s own “no access-control effect” design — recomputed only by SCIM group-membership changes, never by ordinary dashboard Team management, and never applied to a member currently atowner(SCIM can never demote or remove the org’s owner, no exception).
The bearer token is a separate credential from a regular API key (/auth/keys) — it only authorizes the /scim/v2/{org_id}/* surface, nothing else, and can be rotated (POST /orgs/{org_id}/scim/rotate-token) or revoked (DELETE /orgs/{org_id}/scim) independently of SSO.
See Authentication for the key/session/role basics, and API Endpoints — SSO & SCIM for the full endpoint reference.