# SCIM provisioning

> Source: https://truto.one/docs/guides/team-security/scim/

Let your identity provider manage your Truto team: when you assign someone to the Truto app in your IdP, they join the team, and when you unassign or deactivate them, they leave it. Groups your IdP pushes can grant roles and environment access.

:::skip-reading{cli="truto scim-groups list --team-id <team_id>" skill="truto-cli"}
:::

:::callout{type="info"}
Only team admins can manage SCIM. The **SCIM** tab under **Settings** appears only for admins.
:::

## Before you start

You need an active [SSO connection](/docs/guides/team-security/sso) that tells Truto what access new people get. SCIM uses the team's **default** connection: the active connection marked **Set as default connection**, or, if none is marked, the oldest active one. Its **Default role** and **Default environments** are what every provisioned person starts with.

If the team has no active connection, or the default connection's role is **Member** and it has no default environments, every user your IdP tries to create fails with `400`:

```text
Cannot provision user: configure SSO connection default environments first
```

Also verify your email domain on the connection (see [Add and verify your email domain](/docs/guides/team-security/sso#4-add-and-verify-your-email-domain)). SCIM creates the Truto account, and SSO is how the person then signs in. SSO signs people in only on a verified domain.

## 1. Generate a SCIM token

1. Open **Settings → SCIM** ([app.truto.one/settings/scim](https://app.truto.one/settings/scim)).
2. Under **SCIM endpoint**, copy the **SCIM Base URL**:

   ```text
   https://api.truto.one/scim/v2
   ```

3. Under **Bearer token**, click **Generate token**, then **Generate SCIM Token**.
4. Copy the token into your IdP now. Truto shows it only once and stores only a hash of it. Tick **I have copied my SCIM token shown above** and click **Done**.

Your IdP sends this token as `Authorization: Bearer <SCIM token>` on every SCIM request. The token is tied to this team.

To make sure the token works, call the service provider config endpoint:

```bash
curl https://api.truto.one/scim/v2/ServiceProviderConfig \
  -H "Authorization: Bearer <SCIM token>"
```

To revoke the token, click **Revoke token** under **Bearer token**. Provisioning stops until you generate a new token and put it in your IdP.

:::callout{type="warning"}
An API token can't create a SCIM token. `POST /scim-token` needs a team admin, and API-token requests are refused. Generate SCIM tokens in the console.
:::

## 2. Configure your identity provider

In your IdP's SCIM settings for the Truto app (Okta, Microsoft Entra ID and other SCIM 2.0 clients), enter these values:

| What the IdP asks for | Value |
|---|---|
| SCIM base URL (tenant URL) | `https://api.truto.one/scim/v2` |
| Authentication | Bearer token: the SCIM token from step 1 |
| Unique identifier for users | `userName`, set to the person's email address |
| Provisioning actions | Create users, update user attributes, deactivate users, push groups |

Truto doesn't sync passwords. People sign in with SSO.

Your IdP can look up people who already belong to your team before creating them. Truto answers this request:

```bash
curl --get https://api.truto.one/scim/v2/Users \
  --data-urlencode 'filter=userName eq "ada@example.com"' \
  -H "Authorization: Bearer <SCIM token>"
```

### What Truto supports

| Endpoint | Methods |
|---|---|
| `/scim/v2/ServiceProviderConfig`, `/scim/v2/ResourceTypes`, `/scim/v2/Schemas` | `GET` |
| `/scim/v2/Users` | `GET` (list), `POST` (create) |
| `/scim/v2/Users/<id>` | `GET`, `PUT`, `PATCH`, `DELETE` |
| `/scim/v2/Groups` | `GET` (list), `POST` (create) |
| `/scim/v2/Groups/<id>` | `GET`, `PUT`, `PATCH`, `DELETE` |

Limits:

- **Filters.** Only `eq` is supported: `userName eq "..."` or `externalId eq "..."` on Users, and `displayName eq "..."` or `externalId eq "..."` on Groups.
- **Pagination.** Use `startIndex` (1-based) and `count`. `count` defaults to 100, and 100 is the maximum.
- **Not supported:** bulk operations, sorting, ETags and password changes.

Responses use `Content-Type: application/scim+json`. Errors use the SCIM error schema, with `status` and `detail`.

On a user, Truto reads these attributes:

- `userName`: the email address, stored in lowercase. Required, or Truto uses `emails[0].value`.
- `name.formatted` or `displayName`: the person's name. Without either, Truto uses the part of the email before the `@`.
- `externalId`
- `active`: Truto accepts `true` and `false`, as JSON booleans or as strings like Entra's `"False"`.

A person's Truto user ID is their SCIM `id`. Group `members` refer to people by that `id`.

A Truto account belongs to one person across every team they're in. If the email your IdP sends already has an account in another Truto team, creating the user fails with `409 User already exists`. Invite that person from **Settings → Team members** instead.

## 3. Map groups to roles and environments

Groups your IdP pushes appear under **Group mapping** on **Settings → SCIM**. The table has these columns:

| Column | What it shows |
|---|---|
| **IdP Group** | The group's name. Groups without a mapping show an **Unmapped** badge. |
| **Members** | How many people are in the group. |
| **Role** | **None**, **Member** or **Admin** |
| **Environments** | For **Member**, the environments to grant. **Admin** shows **All**. |

Pick a role and environments for a group and click its save button. Choosing **None** deletes the group's mapping. Until your IdP pushes a group, the table shows **No IdP groups synced yet**.

Truto works out each person's access from all their mapped groups:

- **Admin wins.** If any of a person's groups maps to **Admin**, they're an admin of the team, with access to every environment.
- **Environments add up.** Otherwise they're a member with the union of the environments from all their mapped groups.
- **Mappings replace connection defaults.** For someone in at least one mapped group, the mappings decide their role and environments. The SSO connection's defaults don't apply on top, and environments no mapping grants are taken away.
- **Members need an environment.** A **Member** mapping needs at least one environment.
- **Leaving every mapped group falls back.** Someone who leaves all their mapped groups goes back to the default role and environments of the SSO connection they're linked to. If they aren't linked to one of this team's connections, they stay on the team as a member with no environments.
- **The team keeps an admin.** Truto refuses a mapping change that would leave no admin who can sign in. The API answers `409` with `This change would leave the team with no admin. Make another member an admin first.`

Truto recalculates access whenever your IdP changes a group's members and whenever you save a mapping.

To read groups and mappings from the API or the CLI:

:::code-tabs

```bash label="cURL"
# Groups your IdP has pushed, with member_count
curl "https://api.truto.one/scim-group?team_id=<team_id>" \
  -H "Authorization: Bearer <API token>"

# Group mappings
curl "https://api.truto.one/group-mapping?team_id=<team_id>" \
  -H "Authorization: Bearer <API token>"
```

```bash label="CLI"
truto scim-groups list --team-id <team_id>
truto group-mappings list --team-id <team_id>
```

:::

## Deprovisioning

When your IdP deactivates a person (`active: false` in a `PUT` or `PATCH`) or deletes them (`DELETE /scim/v2/Users/<id>`), Truto removes them from the team and from every environment of the team.

- **The account stays.** Truto doesn't delete it, and the person stays a member of any other Truto team they belong to.
- **The person disappears from this team.** A deactivation answers with the user and `active: false`. After that, `GET /scim/v2/Users/<id>` returns `404`, and the person no longer appears in the user list.
- **API tokens keep working.** API tokens belong to an environment, not to the person who created them. Review **Settings → API Tokens** when someone leaves.

To bring someone back, set them to `active: true` in your IdP. Truto adds them back to the team only if their account belongs to no other team. They get the default connection's role and environments, plus whatever their mapped groups still grant. If their account belongs to another team, the request returns `404`, and you need to invite them from **Settings → Team members**.

Under **Sync status**, **Settings → SCIM** shows how many people your IdP has synced and when it last provisioned.

## API and CLI

| Method and path | What it does |
|---|---|
| `GET /scim-token?team_id=<team_id>` | List SCIM tokens: `name`, `created_by`, `last_used_at`. The token value is never returned. |
| `POST /scim-token` | Create a token (`team_id`, `name`). The response includes `token`, once. Needs a signed-in admin. |
| `GET /scim-token/<id>`, `DELETE /scim-token/<id>` | Get or revoke a token. Revoking needs an admin. |
| `GET /scim-group?team_id=<team_id>` | List the groups your IdP pushed. Read-only, because your IdP owns them. |
| `GET /group-mapping?team_id=<team_id>` | List mappings. Filters: `scim_group_id`, `id`. |
| `POST /group-mapping` | Create a mapping (`team_id`, `scim_group_id`, `role`, `environment_ids`). Needs an admin. |
| `PATCH /group-mapping/<id>`, `DELETE /group-mapping/<id>` | Change or delete a mapping. Needs an admin. |

:::callout{type="warning"}
API tokens carry no team role. With an API token you can list tokens, groups and mappings, but creating or revoking tokens and saving mappings return `403`. Make those changes in the console. The CLI signs in with an API token, so its `scim-tokens create`, `scim-tokens delete` and `group-mappings create`/`update`/`delete` commands are refused the same way.
:::

:::code-tabs

```bash label="cURL"
curl "https://api.truto.one/scim-token?team_id=<team_id>" \
  -H "Authorization: Bearer <API token>"
```

```bash label="CLI"
truto scim-tokens list --team-id <team_id>
```

:::

## Next

SCIM relies on your SSO connection's defaults and verified domain. To change those, or to enforce SSO, see [Single sign-on](/docs/guides/team-security/sso). To add people by hand instead, see [Adding team members](/docs/guides/team-members/adding-team-members).
