# SCIM provisioning
Source: https://docs.tensormachine.ai/console-management/scim

## What SCIM does

**SCIM** (System for Cross-domain Identity Management, [RFC 7644](https://datatracker.ietf.org/doc/html/rfc7644))
lets your identity provider (Okta, Microsoft Entra ID / Azure AD, etc.) manage the
lifecycle of your users on Tensor Machine automatically.

SCIM is **optional** and **complements SSO** — you do not need it to sign in. Think of it as two halves of the lifecycle:

| | Sign-in (SSO) | Lifecycle (SCIM) |
|---|---|---|
| **Provisioning** — create a user | Just-in-time on first SSO login | Pushed by your IdP ahead of time |
| **Deprovisioning** — remove access | *(can't — SSO only controls login)* | **Pushed by your IdP — revokes access immediately** |

The single most valuable thing SCIM adds is **automated deprovisioning**: when you
disable or unassign someone in your IdP, their access to your Tensor Machine
organization is revoked right away — no manual clean-up, which is exactly what
security reviews (SOC 2, ISO 27001) expect.

<Callout type="info">
  **JIT vs SCIM.** Without SCIM, users are still provisioned Just-In-Time on their first
  SSO login. Turn SCIM on when you also want your IdP to *deprovision* and manage users
  centrally.
</Callout>

## Set it up

All configuration is self-serve under **Settings → SCIM** (organization owner/admin).

1. **Enable SCIM** for your organization.
2. **Generate a provisioning token.** It is shown **once** — copy it immediately. Only a
   hash is stored; we can never show it again.
3. Copy your **SCIM Base URL** (also on that page). It looks like:

   ```
   https://api.tensormachine.ai/v1/scim/v2/orgs/<your-org-id>
   ```

4. In your IdP's provisioning settings, create a SCIM connector using:
   - **SCIM connector base URL** → the Base URL above
   - **Authentication** → *HTTP Header* / *OAuth Bearer Token* → the generated token
   - **Unique identifier for users** → `userName`

### Okta

*Applications → your app → Provisioning → Configure API Integration → Enable API
integration.* Paste the **Base URL** and **API token**, test the connection, then enable
**Create Users**, **Update User Attributes**, and **Deactivate Users**.

### Microsoft Entra ID (Azure AD)

*Enterprise applications → your app → Provisioning → Automatic.* Set the **Tenant URL**
to the Base URL and the **Secret Token** to the generated token, test, and turn
provisioning **On**.

## Token rotation

There is **one active token per organization**. Generating a new token **immediately
invalidates the previous one** — your IdP will stop syncing until you paste the new token
into it. The console warns you before regenerating and shows the active token's prefix and
when it was generated.

## Supported operations

The **User** resource is fully supported:

| Operation | Method | Notes |
|---|---|---|
| Create user | `POST /Users` | Reconciles into an active membership |
| Read user | `GET /Users/{id}` | |
| List / search | `GET /Users?filter=userName eq "…"` | RFC list response (`totalResults`, `startIndex`, `itemsPerPage`, `Resources`) |
| Update (deactivate/reactivate) | `PATCH /Users/{id}` | `op: replace` on `active` |
| Replace | `PUT /Users/{id}` | Replaces mutable attributes |
| Delete | `DELETE /Users/{id}` | Removes the record + revokes access |

**Attributes** consumed: `userName`, `externalId`, `active`, `name.givenName`,
`name.familyName`, `emails`.

- `userName` is treated as the **immutable natural key** — a request that changes it keeps
  the original (keep `userName` stable in your IdP, as Okta and Entra recommend).
- `externalId` should be your IdP's stable user id (it maps to the same identity your SSO
  login resolves to).

## Deprovisioning behavior

When your IdP marks a user inactive (`active: false` via `PATCH`/`PUT`) or deletes them:

- the user's Tensor Machine **membership is revoked immediately**, and
- API calls made with that user's session are **denied** from that point on.

<Callout type="warn">
  **API keys are not tied to a user session.** If a departing member created project API
  keys, revoke or rotate those keys separately (Console → API Keys). Deprovisioning stops
  their *console/session* access; it does not retroactively kill long-lived keys they
  minted.
</Callout>

## Not supported (yet)

To set expectations honestly:

- **Groups** (`/Groups`) — no group-based provisioning or group→role mapping. Users only.
- **Discovery endpoints** — `/ServiceProviderConfig`, `/ResourceTypes`, `/Schemas` are not
  served. Okta and Entra work without them (configure the connector manually as above);
  a few tools that auto-probe these may warn.
- **Role/entitlement sync** — SCIM-provisioned users receive the default member role;
  change roles in the console (Settings → Members).

Need one of these for a rollout? Contact us — the User lifecycle above covers the vast
majority of deployments.

## Troubleshooting

| Symptom | Likely cause |
|---|---|
| `401 Unauthorized` from the IdP | Token is wrong, was regenerated, or SCIM is disabled for the org |
| Users not appearing | Provisioning not enabled in the IdP, or the app isn't assigned to those users |
| A removed user still has access via an API key | Expected — revoke their API keys (see the note above) |
| `409 Conflict` on create | A user with that `userName` already exists in the org |

## Security

- Tokens are stored **hashed** (SHA-256); the plaintext is shown only once at generation.
- SCIM requests authenticate **only** with the org's provisioning token — a normal
  console/API login token is never accepted on the SCIM endpoint.
- A token is scoped to its organization; it cannot read or write another tenant's users.
