> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gradial.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SCIM User Provisioning

> Automatically provision, update, and deprovision Gradial users, organization roles, and workspace access from your identity provider using SCIM 2.0.

Gradial separates *login* from *provisioning*:

* **SSO (SAML/OIDC)** answers "can this person log in?"
* **SCIM 2.0** answers "should this person have Gradial access, what role should they have, and which workspaces should they see?"

SCIM is **one-way**: your identity provider (IdP) pushes user and group changes into Gradial. Gradial never writes changes back to the IdP.

|                | SSO                          | SCIM                                                |
| -------------- | ---------------------------- | --------------------------------------------------- |
| Primary job    | Authentication / login       | Provisioning / lifecycle / access sync              |
| Direction      | User browser ↔ IdP ↔ Gradial | IdP server → Gradial API                            |
| Typical signal | "This user authenticated"    | "Create / update / deactivate this user or group"   |
| Gradial effect | Establishes a session        | Grants or revokes organization and workspace access |
| If missing     | User cannot log in           | User may not exist, or may retain stale access      |

<Info>
  SSO alone does not silently create a Gradial account. A user must be provisioned (through SCIM, a manual invite where allowed, or another valid linking path) before they can log in. An unprovisioned SSO user sees a clear "account not provisioned" error rather than ending up with a silent orphan account.
</Info>

***

## How to Enable SCIM

SCIM requires two gates:

1. The SCIM provisioning feature must be enabled for your organization.
2. An organization owner must turn on **SCIM Provisioning** in organization settings.

Gradial admins configure SCIM under **Settings → Developer → SCIM Provisioning**:

* Toggle SCIM provisioning on or off (organization owners only)
* Copy the SCIM base URL
* Create and revoke SCIM bearer tokens
* Add SCIM group → workspace mappings (after groups have been provisioned)

Managing SCIM tokens and mappings requires organization settings permission.

***

## SCIM Tokens

Gradial issues a dedicated bearer token for SCIM. Treat it like any other sensitive credential.

* **Prefix:** `gxs_`
* **Scope:** exactly one organization — a token cannot reach any other organization
* **Visibility:** shown once at creation, so copy it immediately
* **Expiration options:** 30, 60, 90, 180, 365 days, or no expiration
* **Usage:** sent in the `Authorization: Bearer <gxs_...>` header

<Warning>
  SCIM tokens can only access SCIM endpoints — they cannot call the rest of the Gradial API. Regular Gradial API keys cannot access SCIM endpoints. The token's owner must have member-management permission in the organization; a token owned by a member or viewer is rejected.
</Warning>

### Token rotation

<Steps>
  <Step title="Create a new token">
    Create a new SCIM token in Gradial and copy it.
  </Step>

  <Step title="Update your IdP">
    Update the secret token in your IdP, test the connection, and save.
  </Step>

  <Step title="Revoke the old token">
    Revoke the previous token in Gradial.
  </Step>
</Steps>

***

## Base URL and Discovery

Base URL: `https://<your-gradial-host>/api/scim/v2`

Discovery endpoints are served without authentication, because IdP connector setup may probe them before sending a bearer token:

* `GET /ServiceProviderConfig`
* `GET /Schemas`
* `GET /ResourceTypes`

Discovery advertises bearer token authentication, PATCH support, equality-only filters, User and Group resource types, and Gradial's optional organization-role extension schema. Bulk operations, password changes, and sorting are not supported.

***

## Supported Endpoints

### Users

| Method   | Endpoint      | Supported                    |
| -------- | ------------- | ---------------------------- |
| `GET`    | `/Users`      | Yes (list, with filters)     |
| `POST`   | `/Users`      | Yes                          |
| `GET`    | `/Users/{id}` | Yes                          |
| `PATCH`  | `/Users/{id}` | Yes                          |
| `PUT`    | `/Users/{id}` | Not supported                |
| `DELETE` | `/Users/{id}` | Yes (treated as deprovision) |

User lookup filters (equality only; compound or complex filters are not supported):

* `userName eq "..."`
* `externalId eq "..."`
* `id eq "..."`

### Groups

| Method   | Endpoint       | Supported     |
| -------- | -------------- | ------------- |
| `GET`    | `/Groups`      | Yes           |
| `POST`   | `/Groups`      | Yes           |
| `GET`    | `/Groups/{id}` | Yes           |
| `PATCH`  | `/Groups/{id}` | Yes           |
| `PUT`    | `/Groups/{id}` | Not supported |
| `DELETE` | `/Groups/{id}` | Yes           |

Group lookup filters (equality only):

* `displayName eq "..."`
* `externalId eq "..."`
* `id eq "..."`

***

## User Provisioning Behavior

### Creating users (`POST /Users`)

* A new email creates a Gradial user, a SCIM link, and organization membership.
* If the email already belongs to a Gradial user in another organization, that existing global identity is linked into this organization. Access in other organizations is not changed.
* If a SCIM link for that user is inactive, the user is reactivated.
* If an active SCIM user already exists in the same organization, the request returns a uniqueness conflict.
* `userName` is expected to be an email. If `userName` is absent, Gradial can use `emails[].value`.

### Updating users (`PATCH /Users/{id}`)

Supported attributes:

* `active`
* `externalId`
* `userName`, `displayName`, `name.givenName`, `name.familyName`, `name.formatted`, `emails`
* Role attributes (see [Organization role sync](#organization-role-sync))

Behavior notes:

* String booleans such as `"True"` / `"False"` are accepted, since some IdPs send these.
* PATCH operation names are case-insensitive (`Add`, `Replace`, `Remove`).
* Unknown attributes are ignored, so IdP extension attributes do not fail the whole sync.
* Password updates are rejected, not silently accepted.

### Profile ownership

Gradial user profiles are global across organizations. For a user who already exists in Gradial, SCIM does not rewrite shared global profile fields (such as name or email), because one customer's IdP should not change how that user appears in every other organization. In practice this means **SCIM can set a user's name only on first provisioning**.

SCIM manages organization-scoped state only: the SCIM external ID, active/deprovisioned state, organization membership, and supported roles.

### Deprovisioning users

Deprovisioning can be triggered by `PATCH active=false` (the common Entra path) or by `DELETE /Users/{id}`. Both have the same effect:

* The user's organization membership is removed.
* The user's SCIM group memberships are removed.
* The user's non-owner workspace memberships (in that organization) are removed.
* The user's sessions scoped to that organization are cleared.
* The user identity and SCIM link are **preserved** for audit, so the user can later be reprovisioned with the same Gradial identity.

<Warning>
  Owners cannot be deprovisioned by SCIM. Removing an owner returns a conflict — the owner must be downgraded or transferred manually first.
</Warning>

***

## Organization Role Sync

SCIM can set the organization role of SCIM-managed users to `admin`, `member`, or `viewer`. `owner` is not SCIM-manageable: assigning it is rejected with a conflict, and owners always remain Gradial-managed.

Roles are read from two places:

1. The standard SCIM `roles` array, especially `roles[primary eq "True"].value`
2. Gradial's extension schema: `urn:gradial:params:scim:schemas:extension:organization-role:2.0:User` with `organizationRole`

Standard `roles` wins when present and parseable. Unknown role values are ignored.

IdP-managed non-owner roles cannot be manually edited in Gradial — the People page shows these rows with disabled role controls.

***

## Workspace Access Through Group Mappings

SCIM does not let the IdP directly create arbitrary Gradial groups or workspaces. Instead, access flows through admin-managed mappings:

1. The IdP provisions SCIM group identities and memberships.
2. A Gradial admin maps a provisioned IdP group to a Gradial workspace (and a workspace role) under **Settings → Developer → SCIM Group Mappings**.
3. Gradial reconciles workspace membership from the mapping and the latest group membership facts.

Mapping fields:

* **IdP Group** — a previously provisioned SCIM group
* **Workspace** — an existing workspace, or "create or reuse a workspace from this IdP group"
* **Workspace role** — `viewer`, `member`, or `admin`

### Workspace role rules

* `owner` is not a SCIM-managed workspace role. Workspace owner rows are always Gradial-managed and are never inserted over, updated, downgraded, or deleted by SCIM.
* Non-owner workspace membership and roles are IdP-managed for mapped workspaces.
* One IdP group maps to one workspace; multiple IdP groups can map to the same workspace.
* If a user is in multiple mapped groups for the same workspace, the highest role wins: `admin > member > viewer`.
* If a user is removed from an admin group but remains in a member/viewer group for the same workspace, Gradial downgrades rather than revokes.
* If a user is absent from every mapped group for that workspace, Gradial removes their non-owner workspace membership.

### Mapping lifecycle

* Creating a mapping immediately reconciles existing SCIM group membership.
* Deleting a mapping reconciles the affected workspace and can remove access for users whose only path was that mapping. The workspace itself is not deleted.
* Re-adding a mapping can reuse a workspace previously associated with that IdP group.
* The mapping UI shows an impact preview before saving (grants, revokes, and role changes).

For SCIM-mapped workspaces, manual changes to non-owner workspace members are blocked by the backend, not just disabled in the UI.

***

## Manual Invites in SCIM-Managed Organizations

When SCIM provisioning is enabled for an organization:

* Local email/password signup and manual invites cannot create or grant initial non-owner access in that organization.
* Owners are still managed in Gradial.
* The People page indicates which members are IdP-provisioned versus locally provisioned, so admins can verify that all non-owner access is coming through SCIM.

***

## Microsoft Entra ID Setup

Gradial's SCIM implementation is currently validated for Microsoft Entra ID only (see [Current limitations](#current-limitations)). The steps below are the Entra-specific setup.

<Steps>
  <Step title="Enable SCIM in Gradial">
    An organization owner turns on SCIM Provisioning under **Settings → Developer**. Create a SCIM token and copy it immediately.
  </Step>

  <Step title="Configure Entra admin credentials">
    In **Microsoft Entra admin center → Enterprise applications → Gradial → Provisioning → Admin credentials**:

    * Provisioning Mode: **Automatic**
    * Tenant URL: `https://<your-gradial-host>/api/scim/v2`
    * Secret Token: your `gxs_...` token

    Click **Test Connection**, then save.
  </Step>

  <Step title="Configure user mappings">
    In **Mappings → Provision Microsoft Entra ID Users**, verify the effective mappings. The key one is `externalId`, which must be mapped to a stable unique Entra identifier (commonly `objectId`). `userName` should be the user's email and should match the email used for SSO.

    | Entra source                  | SCIM target                    | Notes                                                                                                            |
    | ----------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
    | `userPrincipalName` or `mail` | `userName`                     | Must be an email and should match the SSO email. Use `mail` if UPN is not the login email.                       |
    | `objectId`                    | `externalId`                   | The most important mapping. Use a stable unique Entra identifier; `objectId` is recommended over `mailNickname`. |
    | `givenName`                   | `name.givenName`               | First name. Include this so provisioned users show their name in Gradial.                                        |
    | `surname`                     | `name.familyName`              | Last name. Include this together with `givenName`.                                                               |
    | `mail`                        | `emails[type eq "work"].value` | Optional fallback if `userName` is absent.                                                                       |

    Do not map passwords — Gradial rejects password updates. Name mappings only take effect on **first provisioning**.
  </Step>

  <Step title="Enable group provisioning">
    In **Mappings**, enable **Provision Microsoft Entra ID Groups**. This is required for Gradial workspace access: without it, Gradial receives users but no groups, and the Gradial group-mapping picker will be empty.

    Recommended group mappings:

    | Entra source  | SCIM target   |
    | ------------- | ------------- |
    | `displayName` | `displayName` |
    | `objectId`    | `externalId`  |
    | `members`     | `members`     |
  </Step>

  <Step title="Optional: sync organization roles">
    If you want Entra to control Gradial organization roles, the final SCIM value must be one of `admin`, `member`, or `viewer`.

    **Simplest option:** define Entra app-role values exactly as `admin`, `member`, and `viewer`, then map:

    * Mapping type: **Expression**
    * Expression: `SingleAppRoleAssignment([appRoleAssignments])`
    * Target attribute: `roles[primary eq "True"].value`
    * Apply this mapping: **Always**

    **If your Entra app-role names differ**, use an expression that translates them to Gradial's vocabulary, for example:

    ```text theme={null}
    Switch(SingleAppRoleAssignment([appRoleAssignments]), "", "YourReadOnly", "viewer", "YourAuthor", "member", "YourAdmin", "admin")
    ```

    Entra `Switch` comparisons are case-sensitive. Assign only one app role per user for this mapping — `SingleAppRoleAssignment` is a single-role helper.
  </Step>

  <Step title="Assign users and groups">
    In **Users and groups**, assign the users and groups that should have Gradial access.

    Set **Provisioning Status** to **On**. Use **Provision on demand** to test a single user and a single group before letting the full cycle run.
  </Step>

  <Step title="Map groups to workspaces in Gradial">
    After Entra provisions groups, a Gradial admin maps each Entra group to a Gradial workspace under **Settings → Developer → SCIM Group Mappings**, choosing a workspace role of `viewer`, `member`, or `admin`.
  </Step>
</Steps>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="&#x22;No groups available&#x22; in the Gradial mapping picker">
    The group picker only shows IdP groups that Gradial has actually received. If it is empty:

    1. Confirm SCIM provisioning is enabled in Gradial.
    2. Confirm the SCIM token and base URL in Entra match the intended Gradial organization.
    3. Confirm the IdP group is assigned to the Entra enterprise application.
    4. Confirm Entra provisioning has **Provision Microsoft Entra ID Groups** enabled. This is a common root cause: Entra provisions users but never sends group calls.
    5. Use **Provision on demand** for the group, or restart provisioning.
    6. Wait for the next Entra polling cycle if not forcing a sync.
    7. Confirm the group is not already mapped — already-mapped groups are removed from the available-groups picker.
  </Accordion>

  <Accordion title="Unprovisioned users cannot log in">
    This is expected. Provision the user through SCIM first (assign them to the Entra enterprise app and run provisioning), then have them log in again.
  </Accordion>

  <Accordion title="SSO sign-in fails with &#x22;Sign-In Couldn't Be Completed&#x22; for some users">
    Gradial requires an email claim to complete SSO. Azure AD only emits the `email` claim when the user's `mail` attribute is populated — it does not synthesize one from the user principal name (UPN), and adding `email` to the app registration's optional claims does not fix this on its own. Accounts without `mail` (commonly service, shared, or non-mailbox accounts) therefore see this error, while users with a normal mailbox sign in normally — which is why it often affects only a subset of users.

    **Fix:** have your Entra admin populate the `mail` attribute on the affected accounts.

    **Prevention:** before enabling Gradial SSO, have your Entra admin verify that every user who will sign in has a populated `mail` attribute. Catching mailless accounts during rollout planning is much cleaner than after a user hits the error page.
  </Accordion>

  <Accordion title="Deprovisioned user still appears">
    A deprovisioned user's identity is preserved on purpose for audit and reprovisioning. Their access (organization membership and workspace access) is removed, and they cannot log in. If they reappear with access, confirm the IdP has not reactivated or re-assigned them.
  </Accordion>

  <Accordion title="Entra quarantined the provisioning job">
    Entra can quarantine an app and slow its sync cadence if error rates are high. Check the Entra provisioning logs for failing requests, verify the SCIM token is valid and not expired, and verify the Gradial organization still has SCIM enabled. Use **Provision on demand** to isolate a failing user or group.
  </Accordion>
</AccordionGroup>

***

## Current Limitations

* **Microsoft Entra ID is the only IdP currently validated** end-to-end for customer rollout. The implementation follows the SCIM 2.0 standard and is designed to be largely IdP-agnostic (case-insensitive PATCH operations, string booleans, tolerated unknown extension attributes); support for additional IdPs (Okta, Ping, etc.) is on the roadmap and will be documented here when validated.
* **SCIM cannot assign or remove owners**, at either the organization or workspace level.
* **One IdP group maps to one workspace** — multiple groups can map to one workspace.
* **SCIM can set a user's name only on first provisioning**, because Gradial profiles are global across organizations. If users were synced before a name mapping was added, their names must be corrected manually in Gradial.

***

## Related

* [Single Sign-On (SSO) →](/docs/integrations-administration-and-setup/managing-access/sso) — configuring authentication for your identity provider
* [Roles & Permissions →](/docs/integrations-administration-and-setup/managing-access/roles) — the role model SCIM syncs into
* [Adding Team Members →](/docs/integrations-administration-and-setup/managing-access/manage-team-members) — manual invites and the People page
