> For the complete documentation index, see [llms.txt](https://docs.durohub.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.durohub.com/account-management/scim-provisioning.md).

# SCIM Provisioning

Provision and deprovision users and teams in Duro automatically from your identity provider.

{% hint style="info" %}
SCIM works alongside [Enterprise SSO](/account-management/enterprise-sso.md). SAML answers *"who is this person and may they sign in?"* SCIM answers *"which users and teams exist, and who belongs to them?"*
{% endhint %}

## Overview

SCIM 2.0 lets your IdP — Okta, Microsoft Entra ID, JumpCloud, OneLogin, and others — push user and group changes to Duro. When someone joins a group in your IdP, they appear on the matching Duro team. When they leave the company, their Duro access is revoked without a manual step.

Duro exposes a SCIM 2.0 endpoint at a per-tenant host — `https://<tenant>-scim.durohub.com/scim/v2` — separate from the API host you use for GraphQL. Your IdP authenticates with a bearer token pinned to one organization.

### What SCIM manages, and what it doesn't

| SCIM provisions                    | Duro manages                                              |
| ---------------------------------- | --------------------------------------------------------- |
| Users (create, update, deactivate) | Roles on teams — organization role plus library overrides |
| Groups → Teams                     | Which teams get which access                              |
| Group membership → Team membership | —                                                         |

{% hint style="warning" %}
**Roles are always assigned in Duro, never by the IdP.** SCIM provisions team *membership* only; Duro does not consume the SCIM `roles` attribute. After a team is provisioned, a Duro administrator assigns its organization role and any per-library overrides. See [Role-Based Access Control](/advanced-topics/rbac.md#team-based-access-control).
{% endhint %}

## Before you start

* **SAML SSO already enabled** for the organization. Enabling SCIM without it is rejected — complete [Enterprise SSO](/account-management/enterprise-sso.md) first
* **A role with the `organization.scim.manage` permission** — Site Admin by default
* **Administrator access to your IdP** to configure a provisioning app
* **A plan including the `SCIM` entitlement**, or enabling it fails with `FEATURE_NOT_IN_PLAN`. See [Entitlement Errors](/advanced-topics/error-handling.md#entitlement-errors)

**Where this lives in Duro:**

```
https://durohub.com/org/@<your-org-slug>/settings/authentication
```

Or navigate there: **Organization settings → Authentication**. SCIM sits under **Single sign-on**, and shows *Requires SAML SSO* until SAML is on.

{% hint style="info" %}
Disabling SAML disables SCIM with it. You cannot have SCIM active without SAML active.
{% endhint %}

## Step 1 — Enable SCIM

Toggle **SCIM provisioning**, or:

```graphql
mutation {
  organization {
    configureScim(input: {
      id: "550e8400-e29b-41d4-a716-446655440000"
      scimEnabled: true
    }) { id }
  }
}
```

## Step 2 — Generate a token

Click **Generate token** in the SCIM section, or:

```graphql
mutation {
  scimTokens {
    create(input: { name: "Okta production", expiresAt: "2027-08-19T00:00:00Z" }) {
      id
      rawToken
    }
  }
}
```

{% hint style="warning" %}
**The token is shown exactly once** and cannot be retrieved afterwards. Copy it straight into your IdP.
{% endhint %}

`expiresAt` is optional. To see what exists:

```graphql
query {
  scimTokens {
    list { id name lastUsedAt revokedAt expiresAt isExpired }
  }
}
```

To rotate: generate the new token, update your IdP, **then** revoke the old one.

```graphql
mutation { scimTokens { revoke(id: "…") } }
```

{% hint style="warning" %}
**Revoking your last active token releases every SCIM-managed team back to Duro-managed control**, and generating a new one does not undo that. Always generate first.
{% endhint %}

## Step 3 — Configure your IdP

In your IdP's provisioning settings for the Duro application:

| Field                 | Value                                                           |
| --------------------- | --------------------------------------------------------------- |
| SCIM version          | **SCIM 2.0**                                                    |
| Authentication method | **HTTP Header / Bearer token**                                  |
| SCIM base URL         | `https://<tenant>-scim.durohub.com/scim/v2` — no trailing slash |
| Bearer token          | The token from Step 2                                           |

{% hint style="info" %}
**Your SCIM host mirrors your API host.** Both are named for your tenant, so swap `api` for `scim`:

| Your API host          | Your SCIM base URL                      |
| ---------------------- | --------------------------------------- |
| `acme-api.durohub.com` | `https://acme-scim.durohub.com/scim/v2` |

It is **not** the API host itself — SCIM answers only on its own hostname, and requests to `/scim/v2` on the API host return `404`. The path always ends in `/scim/v2` with no trailing slash.

A quick way to confirm you have the right host before configuring your IdP: an unauthenticated request should return `401`, not `404`.

```bash
curl -s -o /dev/null -w '%{http_code}\n' https://acme-scim.durohub.com/scim/v2/Users
# 401 — correct host, waiting on your token
# 404 — wrong host or wrong path
```

{% endhint %}

Run **Test Connection**, then activate provisioning. Most IdPs then sync every user and group already assigned to the app.

## Step 4 — Map groups and assign roles

1. **Assign groups** to the Duro provisioning app in your IdP. Each becomes a Duro team, with its members added
2. **Assign roles in Duro.** For each provisioned team, set its organization role and any per-library overrides

## Groups map to teams

| SCIM operation                                        | Duro effect                                      |
| ----------------------------------------------------- | ------------------------------------------------ |
| **Create Group** (`POST /Groups`)                     | Creates a team                                   |
| **Add / remove members** (`PATCH /Groups`)            | Adds or removes those users from the team        |
| **Replace members** (`PUT /Groups`)                   | Replaces the team's membership                   |
| **Delete Group** (`DELETE /Groups`)                   | Archives the team                                |
| **Deactivate user** (`PATCH /Users`, `active: false`) | Removes the user from all teams, revoking access |

### SCIM-managed teams are locked in Duro

| Locked — managed by your IdP    | Allowed — managed in Duro              |
| ------------------------------- | -------------------------------------- |
| Renaming the team               | Assigning the team's organization role |
| Adding or removing members      | Assigning per-library role overrides   |
| Archiving or restoring the team | Adjusting the team's library access    |

Your IdP owns who is on the team; Duro owns what the team can do.

## Deprovisioning

Deactivating a user in your IdP — or removing them from every group assigned to Duro — removes their team memberships and revokes their organization access. **It also ends every live Duro session for that user**, including any CAD plugin session, within one access-token lifetime.

{% hint style="info" %}
**The account itself persists.** Duro keeps it dormant so authorship, change orders, and comments stay attributable. Reactivating the user in your IdP restores their memberships and access.
{% endhint %}

## Live enforcement

The SCIM-enabled state is checked on every request. Disabling SCIM — directly, or by disabling SAML — stops the bearer token authenticating immediately, and your IdP starts receiving `401 Unauthorized`.

## Testing your setup

1. Confirm SAML is enabled and SCIM shows as enabled
2. Run your IdP's **Test Connection** against the `/scim/v2` base URL
3. Assign a test user, confirm they appear as active in Duro
4. Assign a group with members, confirm a matching team is created
5. Assign a role to the team in Duro, confirm a member's access reflects it
6. Deactivate the test user, confirm access is revoked and the account remains

## Troubleshooting

**Provisioning suddenly returns `401 Unauthorized`** Expected if SCIM was disabled, or SAML was disabled (which disables SCIM). Re-enable SAML, then SCIM, and generate a fresh token if the old one was revoked.

**Test Connection fails before any users sync** Check the host first. A wrong hostname fails at DNS or routing, before your token is ever read, so most IdPs report it as a generic connection problem with nothing to go on — and Duro shows the token as never used, because it never arrived. Run the `curl` check above: `401` means the host is right, `404` or a connection error means it is not. Only then check that the base URL ends in `/scim/v2` with no trailing slash, that the token was copied correctly and is not revoked, and that both SCIM and SAML are enabled.

**A team can't be renamed or edited in Duro** SCIM-managed teams are locked. Make membership and naming changes in your IdP; assign roles and library access in Duro.

**Teams stayed locked after revoking the last token** Release them: `mutation { scimTokens { forceRelease } }`

**A provisioned user's SSO sign-in fails** SCIM is not involved in sign-in itself — that is the SAML flow. A rejected assertion returns the user to whichever page they started from with `saml_failed` or `saml_no_account` in the URL; work from [Where a failed sign-in lands](/account-management/enterprise-sso.md#where-a-failed-sign-in-lands) and the [SSO troubleshooting](/account-management/enterprise-sso.md#troubleshooting) section. A user provisioned through SCIM already has an account, so `saml_no_account` points at an address mismatch between your directory and the assertion, not at a missing account.

**A provisioned user can sign in but sees no access** Access flows through teams. Confirm they are on a provisioned team, and that the team has a role assigned in Duro.

## Next steps

[Role-Based Access Control](/advanced-topics/rbac.md#team-based-access-control) — how team roles combine with a user's direct roles.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.durohub.com/account-management/scim-provisioning.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
