<!-- section: Auth API · status: building · source: docs/auth-api/11-directory-sync.md -->

> SCIM provisioning — issue a directory a credential, and let it create and deactivate people.


# Directory sync

[SSO](/docs/auth-api/enterprise-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.

## POST /v1/auth/scim-connections

Issue the directory a credential. **Secret key**, from your server, naming the
organization and the administrator doing it.

```bash Request
curl -X POST "$BASE/v1/auth/scim-connections" \
  -H "authorization: Bearer $SK" \
  -H "content-type: application/json" \
  -d '{"organizationId":"org_…","actorId":"user_…"}'
```

```json Response
{
  "token": "ba_scim_credential_…",
  "scimBaseUrl": "https://<your-deployment>/v1/scim/<projectId>/v2"
}
```

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 <token>`.

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](/docs/auth-api/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:

```bash Request
curl -X PATCH "$BASE/v1/auth/settings" \
  -H "authorization: Bearer $SK" \
  -H "content-type: application/json" \
  -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 |
