SCIM provisioning
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
SCIM provisioning
Auto-create, update, and de-provision Chatly seats from your IdP via SCIM v2.
SCIM 2.0 is how identity providers automate the seat lifecycle. New hire → Chatly seat appears on the next push. Employee departs → membership deactivated and every session revoked.
Chatly implements the Users half of SCIM v2, plus the three discovery documents a connector reads at setup. It is built against the shapes Okta, Microsoft Entra, and OneLogin actually send.
Info — SSO + SCIM solve different problems
SSO is how users sign in (authentication). SCIM is who can sign in (provisioning). You almost always want both — SSO without SCIM means admins manually invite every new hire. See SSO setup first.
Warning — Groups are not implemented
There is no
/Groupsresource.GET /ResourceTypesadvertisesUserand nothing else, and a PATCH whose path isgroupsis refused withinvalidPath. If your connector is configured to push group memberships, turn that off — it will error rather than no-op.
Enable SCIM
Workspace → SSO → SCIM → Generate token.
Copy the token immediately — it is hashed server-side and never shown again.
In your IdP, add a SCIM connection with:
Parameters
Name | Type | Description |
|---|---|---|
|
|
|
|
|
|
|
| HTTP Header — |
Managing tokens
POST /v1/sso/scim/tokens — Bearer token
GET /v1/sso/scim/tokens — Bearer token
DELETE /v1/sso/scim/tokens/{id} — Bearer token
These three are dashboard-scoped (workspace:write), not SCIM-scoped. POST takes an optional description (1–200 chars) so an operator can tell tokens apart in the list, and returns the token exactly once. GET lists tokens without their hashes. DELETE revokes one — a revoked token is refused with the same error as an unknown one.
Supported endpoints
Implemented
Name | Type | Description |
|---|---|---|
|
| List, with |
|
| Fetch one. |
|
| Create. 201 on success; |
|
| Full replace — how connectors that do not implement PATCH deprovision. |
|
| Sparse update. Toggling |
|
| Deactivate, do not erase — identical to |
|
| Capability discovery — your connector calls this once at setup. |
|
| Lists |
|
| The User schema, trimmed to the attributes we actually store. |
ServiceProviderConfig advertises exactly what is true: patch supported, filter supported with maxResults: 200, and bulk, sort, etag, changePassword all unsupported.
The id is a membership id
SCIM resource ids are membership ids, not user ids. A person can be in several workspaces; your IdP is provisioning into exactly one, and the membership is the row that gets created, deactivated, and reactivated.
Filters
Only two filter shapes are accepted, because they are the only two Okta, Entra, and OneLogin send:
userName eq "[email protected]"
externalId eq "00u1a2b3c4"Anything else is refused with 400 invalidFilter. That refusal is deliberate: a filter we do not understand returning the whole workspace reads to the IdP as "none of these users are new".
Attribute mappings
Attributes we store
Name | Type | Description |
|---|---|---|
|
| Must be an email address. Stored lowercased on |
|
| Combined with |
|
| See above. |
|
| Used ahead of the assembled given/family pair. |
|
| Preferred over everything else. Order is |
|
|
|
|
| Persisted on |
|
| Accepted by the schema for connector compatibility. The identity always comes from |
Attributes we accept and knowingly drop
Name | Type | Description |
|---|---|---|
|
|
|
|
| An unrecognised PATCH path is |
Roles
Warning — SCIM always provisions the
agentroleEvery membership SCIM creates gets the role
agent. There is no group mapping, no role attribute, and no Chatly SCIM schema extension. Change someone's role from the Team page after they are provisioned; a later SCIM push will not overwrite it.
If you need role-on-provision, the SSO connection's jitRole sets the role for users provisioned by an SSO login instead — see SSO setup. It is also a single role per connection, not a group mapping.
De-provisioning
When the IdP sends active: false (via PATCH, PUT, or DELETE), inside one transaction we:
Set
memberships.deleted_at = now()— the membership stops being live and stops consuming a seat.Revoke every unrevoked session row for that user in that workspace, so nothing can refresh into a new access token.
Revoke the user's push registrations, so their phone stops receiving customer messages.
Write an audit row (actor: SCIM).
An already-issued access token keeps working until it expires — JWT_ACCESS_TTL_SECONDS, 15 minutes by default. Nothing force-closes an open WebSocket.
Re-activation (active: true after a false) restores the same membership — conversations, macros, and report history stay attached — subject to the workspace's seat limit. The role is unchanged by the restore.
Name, email, and externalId changes are applied but deliberately not audited: they are the highest-volume thing SCIM does and none of them move access.
Push immediately, not on schedule
Most IdPs default to event-driven SCIM. If yours polls, the worst case from "user leaves" to "seat revoked" is one poll interval.
Warning — No polling on our side
Chatly is the SCIM server — we act only when your provider calls us. There is no pull, no reconciliation job, and no "re-sync" button in the dashboard. If a user is missing, the fix is a full sync from the IdP.
Token hygiene
The SCIM bearer token is functionally an admin credential — anyone with it can create or deactivate members of the workspace.
Danger — Treat SCIM tokens like AWS root keys
Store the token in your IdP's secret manager, not in code. Mint one per provider so you can revoke a single connector. If you suspect leakage, Revoke in the dashboard and mint a new one — a revoked token stops working immediately.
Troubleshooting
Info — 409 on POST /Users
A live membership already exists for that
userNameorexternalId. That is the RFC 7644 §3.3 answer and it is what tells Okta and Entra to switch to an update — most connectors handle it automatically. A 201 here would make the provider keep two records for one person.
Info — 400 invalidPath on PATCH
Your connector sent an attribute path we do not store and have not listed as ignorable —
groupsis the usual one. Remove it from the connector's attribute mappings.
Info — 403 on every SCIM call
The
Authorizationheader is missing, is notBearer …, is duplicated (a repeated header arrives as an array and is treated as malformed), or names a token that was revoked. Revoked and unknown tokens return the same error on purpose.
Info — Errors read strangely in the IdP admin UI
They should not — SCIM responses use the RFC 7644 §3.12 error body, whose
detailstring is written for the person configuring the connector. If you are seeing the platform's own JSON error envelope, you are calling a non-SCIM route.