Auth API
buildingSCIM provisioning — issue a directory a credential, and let it create and deactivate people.
Directory sync
SSO answers "can this person sign in". SCIM answers "who exists at all" — a company that hires someone on Monday expects them to have an account before they first open your app, and expects it gone the day they leave. Neither works if people only appear when they sign in.
Issue the directory a credential. Secret key, from your server, naming the organization and the administrator doing it.
1curl -X POST "$BASE/v1/auth/scim-connections" \2 -H "authorization: Bearer $SK" \3 -H "content-type: application/json" \4 -d '{"organizationId":"org_…","actorId":"user_…"}'1{2 "token": "ba_scim_credential_…",3 "scimBaseUrl": "https://<your-deployment>/v1/scim/<projectId>/v2"4}The token is returned once and stored only as a keyed digest — it can be rotated, never read back.
actorId must be an owner or admin of that organization; a plain member gets
403, because a directory credential can create and deactivate anybody in it.
GET /v1/auth/scim-connections lists what exists, without tokens.
Hand over the base URL and token
Your enterprise customer's IT team pastes both into Okta, Entra or whatever they
run. Authentication is Authorization: Bearer .
Note the base URL carries your project id rather than authenticating by API
key. SCIM reserves the Authorization header for the directory's own token — a
directory cannot send two — so the project moves into the path. It is not a
secret; the token is what authorizes anything.
What the directory drives
POST /Users | Someone joins — a real user account, before they ever sign in |
PATCH /Users/:id | active: false when they leave, which ends their sessions |
DELETE /Users/:id | Removes the directory's resource |
POST /Groups | Groups and memberships |
GET /Users?filter=… | Equality filters on userName, externalId, emails.value |
Deleting a SCIM resource does not delete the person. Their account, their data and anything you attached to it survive; the directory is disclaiming management, not issuing an erasure. Use delete users for that.
Provisioning creates no way to sign in. A provisioned person still needs SSO, a password or a passkey — SCIM says who exists, not how they authenticate. Pair it with an SSO provider on the same organization and the two halves meet.
Discovery endpoints (/ServiceProviderConfig, /Schemas, /ResourceTypes) are
public, so a directory can inspect what is supported before you hand it a token.
Letting groups grant roles
By default a directory group is synced and grants nothing — a group is a fact about someone else's directory until you say what it should mean in your product. Say it with an explicit map:
1curl -X PATCH "$BASE/v1/auth/settings" \2 -H "authorization: Bearer $SK" \3 -H "content-type: application/json" \4 -d '{"scimRoleMap":{"directory-group-admins":"admin"}}'The key is the group's externalId, falling back to its display name. Prefer the
externalId: a directory administrator can rename a group, and if a bare name
granted access then a rename would be a privilege change.
Only admin and member can be granted this way. owner is refused — the
owner can delete the organization and remove everyone in it, and that is not a
decision to hand to whoever administers an identity provider.
Two things this deliberately will not do:
It never touches an owner. However the groups change, an owner stays an owner. A directory cannot lock a customer out of their own organization.
Losing a group demotes, it does not remove. Someone dropped from every mapped
group becomes a member rather than losing their membership, because an
administrator moving people between groups should not delete their access to
their work.
Within those rules the directory owns the role: promote a synced user by hand and the next sync will put them back where the directory says they belong.
Errors
| Status | Code | Means |
|---|---|---|
403 | — | actorId is a plain member of that organization |
401 | — | The directory's bearer token is unknown or rotated |
404 | — | No such project id in the base URL |