> ## Documentation index
> Fetch the complete documentation index at: https://rolter-ai-rolter.docs7.io/llms.txt
> Ask it with GET https://rolter-ai-rolter.docs7.io/ask?q=<keywords or question>. Short keywords rank best.
> Use these to discover all available pages before exploring further.

# Provision Users into Rolter from Okta, Entra ID, or Any SCIM 2.0 Identity Provider

> Issue an org-scoped SCIM token, wire it into your identity provider, map its groups to teams, and let joiners, movers, and leavers flow into Rolter automatically.

Rolter speaks SCIM 2.0, so your identity provider can create, update, and deactivate Rolter accounts — and push its groups — without anyone touching the dashboard. You issue a provisioning token for the org, paste it into the IdP's SCIM connector, and the IdP does the rest on its own schedule.

The direction of control matters: **the identity provider drives provisioning, Rolter receives it.** The dashboard never calls the SCIM endpoints. What you manage in **Governance → User Provisioning** is the credential the IdP authenticates with — issuing one, seeing when it last synced, and revoking it — and the address it connects to, which the screen shows so you never assemble it by hand.

## What a provisioning token is

A provisioning token is an opaque bearer credential scoped to exactly one org.

<Info>
The token carries the tenant. There is no org id anywhere in the SCIM URL, which means a token issued for one org physically cannot address another org's users — even if the IdP is misconfigured.
</Info>

Issuing, listing, and revoking tokens all require the **admin** role on the org (see [RBAC](/security/rbac)). A member or viewer sees an explanation instead of the token list.

## Wire up an identity provider

<Steps>
  <Step title="Issue a token">
    Open **Governance → User Provisioning** and choose **Issue token**. Name it after the connector it will live in — `Okta production`, `Entra staging` — because that name is the only thing that identifies it afterwards.
  </Step>

  <Step title="Copy the token and the base URL">
    The plaintext value is displayed exactly once, in the panel that opens right after creation. Rolter stores only a peppered SHA-256 digest of it, so **the value cannot be displayed or recovered later** — not by you, not by support, not from a database dump. If you lose it, issue a new token and revoke the old one.

    Closing the panel before you have copied the token asks "Close without copying?", since the token cannot be shown again; Cancel keeps it open. If the browser will not let the page use the clipboard, which is common on a dashboard served over plain `http`, the panel says the copy failed in a message that stays, and the token stays on screen, selected, so you can copy it by hand with Ctrl+C or Cmd+C.

The same panel shows the **SCIM base URL** under the token, with its own copy button, so the two values the connector needs are in one place. The URL stays on the screen above the token list afterwards, and is the same for every token in the org.

  </Step>

  <Step title="Configure the connector">
    In your identity provider, set the SCIM base URL to the value you copied, which looks like `https://rolter.example.com/scim/v2`, and the authentication mode to HTTP header / bearer token, using the token. Enable *create users*, *update user attributes*, and *deactivate users* — and *push groups* if you plan to drive roles from group membership.

    The host part is the control plane's public URL, `ROLTER_PUBLIC_URL`, as the server reports it through [`GET /api/v1/public-url`](/api/public-url). It is not the address your browser happens to be using: the identity provider calls Rolter from outside, often under a different name than the one you open the dashboard at, and a wrong base URL fails in the provider's own test console without any hint from Rolter.

    <Warning>
    If `ROLTER_PUBLIC_URL` is not set, the control plane falls back to `http://localhost:4001`, which an identity provider can reach only if it runs on the control plane's own host. The screen shows that default with a warning under the URL and under the URL in the token panel. Set `ROLTER_PUBLIC_URL` to an address your identity provider can call and restart the control plane before you paste the URL into the connector.
    </Warning>

  </Step>

  <Step title="Push a test user">
    Assign one person to the Rolter application in the IdP and run a sync. They appear under **Governance → Users**, and the token's **Last sync** column changes from *never used* to today's date on the next request. The token list shows dates only; hover one for the exact time.
  </Step>
</Steps>

## What provisioning does and does not grant

A provisioned account is deliberately minimal:

- It joins the org with the **viewer** role, merely for existing. SCIM decides _who exists_; anything beyond a read comes from a group mapping you wrote (see [Map groups to teams](#map-groups-to-teams)).
- It has **no local password**. A `password` attribute sent by the IdP is ignored rather than stored — provisioned accounts are SSO-shaped by construction.
- Deactivating a user through SCIM also drops their live sessions, so a leaver disabled in the IdP is logged out of Rolter rather than merely blocked from signing in again.
- A SCIM `DELETE` deactivates the account and removes the org's identity mapping, but keeps the underlying user row: its memberships and audit trail have to outlive any one IdP. Re-provisioning the same person adopts the existing account instead of creating a duplicate. When that create carries `active: false` and the deactivation is refused (see below), nothing is linked, so the IdP's retry gets the same `409` rather than a `userName` conflict.
- Deactivating or deprovisioning the deployment's **last active superadmin** is refused with `409`, the same as from the dashboard, so an IdP sync cannot leave nobody able to administer the deployment. Make another account superadmin first.
- Deactivating or deprovisioning an account that holds an organization's **last active admin** grant is refused with a SCIM `409` too, for every organization the account administers, not only the one the token belongs to. The provisioning token cannot repair an organization afterwards the way a superadmin can, so the IdP is told and retries. Grant `admin` to another person first.

- Its **display name** comes from the IdP. SCIM `displayName` is copied onto the account on every create and replace, and the account cannot change it from the dashboard or `PATCH /api/v1/me/profile` (the API answers `409`); `GET /api/v1/auth/me` reports `display_name_managed: true`. An IdP that sends no `displayName` leaves the stored name as it was. The account's **bio** stays its own. A `PUT` with `active: false` that is refused because the account is the last active superadmin (`409`) applies none of its other changes, so the account keeps its `userName`, `externalId` and display name. The account stores the name with surrounding whitespace and control characters removed and cut to 80 characters, while the SCIM resource returns `displayName` exactly as your IdP sent it, so the IdP's reconciliation sees its own value and does not push it again on every sync.

- A `PATCH` carrying `displayName` renames the account the same way, and if two organizations provision the same account the most recent sync from either decides the name.

Every provisioning action is written to the [audit log](/observability/logs-and-cost) with the token id as the actor, since the change was made by a machine and not by a person. Issuing and revoking a token are operator actions and name the operator instead: the token's **created by** field and the `scim_token.create` entry carry the account that issued it, including a superadmin who holds no role in the org. A token issued with the admin token, or in open mode, names nobody. See [who an entry names as the actor](/security/rbac#who-an-entry-names-as-the-actor).

## Supported operations

| Operation            | Endpoint                      | Notes                                                                                                                                                                                        |
| -------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| List / search users  | `GET /scim/v2/Users`          | Supports the `userName eq "value"` filter that IdPs use for reconciliation; other filters are rejected explicitly rather than silently ignored                                               |
| Create user          | `POST /scim/v2/Users`         | Idempotent — a replayed create returns a `uniqueness` conflict instead of a second account                                                                                                   |
| Read user            | `GET /scim/v2/Users/{id}`     |                                                                                                                                                                                              |
| Replace user         | `PUT /scim/v2/Users/{id}`     |                                                                                                                                                                                              |
| Patch user           | `PATCH /scim/v2/Users/{id}`   | The `active` and `displayName` attributes, in both the path and bare-value shapes IdPs send; any other attribute is rejected. A patch that deactivates and renames is applied all-or-nothing |
| Deprovision          | `DELETE /scim/v2/Users/{id}`  | Deactivates and unmaps; the account row survives, and its group-granted roles go with the identity                                                                                           |
| List / search groups | `GET /scim/v2/Groups`         | Supports the `displayName eq "value"` filter                                                                                                                                                 |
| Create group         | `POST /scim/v2/Groups`        | Idempotent — a replayed create returns a `uniqueness` conflict                                                                                                                               |
| Read group           | `GET /scim/v2/Groups/{id}`    |                                                                                                                                                                                              |
| Replace group        | `PUT /scim/v2/Groups/{id}`    | Replaces the member list wholesale                                                                                                                                                           |
| Patch group          | `PATCH /scim/v2/Groups/{id}`  | Member add / remove / replace in every shape IdPs send, plus `displayName` and `externalId`                                                                                                  |
| Delete group         | `DELETE /scim/v2/Groups/{id}` | Removes the group and everything it granted                                                                                                                                                  |

## Map groups to teams

A group arriving from your identity provider grants nothing on its own. **You** decide what it is worth, by writing a mapping — the identity provider never gets to name a role.

**Governance → User Provisioning** carries a **Group mappings** panel under the token list. Type the group's display name, pick where the role applies — the whole organization, a team, or a project — pick the role, and press **Map group**. The scope list covers the whole organization: every team, and every project in any of those teams grouped under the team it belongs to, so a mapping onto a project outside the team the scope switcher currently shows needs no detour through the switcher. An organization with no teams yet says so, and offers only itself. **Role to grant** starts on `viewer`, the least powerful role, and the scope starts on the whole organization. A mapping that grants `admin`, or grants at the whole organization, is confirmed before it is written: the dialog names the group, the role and the scope, says that everyone in that group at your identity provider gets the role straight away, and lists what raised it (the role, the scope, or both). A `member` or `viewer` mapping on one team or one project saves without asking. Once a mapping is written, the form goes back to `viewer` on the whole organization. The panel lists every mapping the org has with its scope and role, and removing one asks first, because it withdraws that role from everyone in the group. A mapping whose team or project cannot be named — the row was deleted, or the account reading the page cannot see it, or the list failed to load — is marked **Unresolved scope** rather than shown as a bare id, and hovering it gives the stored id to quote in a support request. Until a mapping exists the panel says so plainly: the groups your IdP pushes grant nothing, and every provisioned account stays a viewer.

The same thing over the API:

```bash
curl -X POST https://your-rolter-host/api/v1/orgs/$ORG_ID/scim-group-mappings \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"group_name": "platform", "role": "member", "team_id": "'$TEAM_ID'"}'
```

- `group_name` is the group's **display name** as your IdP shows it.
- `role` is one of `admin`, `member`, `viewer` — the same three roles [RBAC](/security/rbac) defines. Nothing else is mappable.
- Name a `team_id` or a `project_id` to grant at that scope; omit both to grant at the org.
- The scope must belong to the same org. A mapping cannot reach another tenant's team, whatever the IdP sends.

This is the same model [single sign-on](/security/single-sign-on) uses for its group mappings, so you only learn it once.

<Info>
Mappings key on the group's display name. If you rename a group in the identity provider, rewrite the mapping too — otherwise the old name simply stops matching and the roles it granted are withdrawn on the next sync.
</Info>

You can write a mapping before the group has ever been pushed. Creating or deleting one takes effect immediately for whoever is already in that group, rather than waiting for the next sync.

## What a sync does to roles

Every group change recomputes the affected people's provisioning-granted roles from scratch:

- Running the same sync twice changes nothing.
- Removing someone from a group revokes exactly what that group gave them, right away.
- A role **you** granted by hand — in the dashboard, through the API, or through an invitation — is never touched by a sync. Manual and provisioned grants coexist.
- Deleting a group, or deprovisioning a user, withdraws the roles involved in the same request.
- The one exception is an organization's **last active admin** grant: a sync that would withdraw it keeps it, still answers success, and records `membership.last_admin_kept` in the audit log. The next sync that touches that person withdraws it once someone else holds `admin` on the organization. Deleting a group mapping from the dashboard or the API follows the same rule for an org admin, but a **superadmin** deleting the mapping withdraws even the last admin grant, exactly as a superadmin revoking it directly would.

<Warning>
Because a mapping is a way to grant a role, creating or deleting one requires the **admin** role on the org — the same bar as granting a role directly.
</Warning>

## Rotating and revoking a token

Revocation takes effect on the very next request — the token is resolved from the database on every call, so there is no cache to wait out.

<Warning>
Revoking a token stops the sync. It does **not** touch the accounts that token already provisioned: nobody is deactivated, nobody is logged out, and no membership is removed. Those accounts simply stop receiving updates from the IdP until you point it at a new token.
</Warning>

To rotate without a gap in coverage:

1. Issue a second token and copy it.
2. Update the connector in the identity provider to use the new value.
3. Run a sync and confirm the new token's **Last sync** moves from _never used_ to a date (hover it for the exact time).
4. Revoke the old token.

If you need to cut off an identity provider immediately — a compromised connector, for example — revoke the token first and deactivate the affected accounts from **Governance → Users** separately. Revocation alone is not a way to lock people out.
