SCIM directory sync
How your customer’s directory pushes users and groups into the realm of an account, and the routes it uses.
RealmSSO accepts SCIM 2.0 pushes of users and groups from your customer's directory. Not yet tested against Okta or Entra ID. Group sync and delete-time deprovisioning are off by default. Live now.
What SCIM does here
A SCIM push writes a directory row for each user and each group. A directory row lives in the RealmSSO database. With auto-provision on, a user push also creates or updates the user in the realm of the account. A realm is the separate sign-in space of one account.
SCIM belongs to the account. One bearer token identifies one account, and no route path holds an account id. Your customer's directory calls RealmSSO. RealmSSO does not call the directory.
The token
Your customer chooses the SCIM bearer token. It must have at least 32 characters. RealmSSO stores only the SHA-256 hash of the token. An account has one token. A new token replaces the old one at once. There is no overlap.
The IT admin turns SCIM on and sets the token in the Admin Portal. This is all that the portal does for SCIM. The portal can generate a token in the browser. RealmSSO puts no separate rate limit in front of the token check.
The switches
Live now · API only. Four settings decide what a push does in the realm. No screen sets the last three.
| Switch | API field | Default | Effect |
|---|---|---|---|
| Auto-provision | autoProvision | On | A user push creates or updates the user in the realm. |
| Auto-deprovision | autoDeprovision | Off | DELETE /Users/:id disables the user in the realm. |
| Group sync | groupSync | Off | A group push creates, renames and deletes the group in the realm, and syncs its members. |
| Attribute mapping | attributeMapping | Empty | Copies named SCIM attributes into user attributes in the realm. It applies with auto-provision on. |
Only PUT /api/v1/admin-portal/connections/:id/scim sets auto-deprovision, group sync and attribute mapping. The route needs an Admin Portal session, which starts from an invite link. The :id in the path is ignored, because SCIM settings belong to the account. The same route also takes enabled, endpoint, bearerToken and autoProvision.
Provisioning and deprovisioning
| Request | Directory row | User in the realm, with auto-provision on (default) |
|---|---|---|
POST /Users | Written. | Created or updated by userName. The user has no password. RealmSSO marks the email as verified. |
PUT or PATCH with active: false | Marked inactive. | Disabled. RealmSSO then asks the sign-in service to end the sessions of the user. |
DELETE /Users/:id | Removed. | Disabled, and the sessions are ended, only when auto-deprovision is on. The default is off. Otherwise the user stays as it is. |
With auto-provision off, POST, PUT and PATCH write the directory row only. They change nothing in the realm. A deactivation with active: false follows auto-provision, not auto-deprovision. So a deactivation works in a default account.
RealmSSO disables the user first. Then it asks the sign-in service to end the sessions. If that second step fails, the user stays disabled and RealmSSO writes a warning to its log. A replace with no path never deactivates a user. See the gaps below.
Groups
A group push always writes a directory row. With group sync off, that is all. With group sync on, RealmSSO also creates, renames and deletes the group in the realm and syncs its members. Group sync is off by default and works when you switch it on.
One gate for each account
RealmSSO refuses every SCIM write with 403 when the realm of the account is a shared realm. A shared realm here is the old shared realm, or a realm that the sign-in service uses for itself. Reads still work. Each new account has its own realm, so it does not meet this rule. The SCIM settings of the account must also be on. If they are off, the token check answers 401.
Events
A SCIM write records an event, on success and on error. A write refused before it starts records none: a bad token, a refused write, an unknown id or a body that fails validation. The event types are scim.provisioned, scim.updated, scim.deprovisioned, scim.group_provisioned, scim.group_updated and scim.group_deprovisioned. A read records no event. RealmSSO keeps events for 90 days by default. Read them with GET /api/v1/observability/events and the eventType filter.
Not yet tested against Okta or Entra ID
The routes follow SCIM 2.0. We have not run a connector from Okta or Entra ID against them. These four gaps could affect a connector:
opmust be lowercase:add,removeorreplace. A capitalisedReplacereturns400.- A
replacewith nopathand{ "active": false }as the value is ignored. RealmSSO changes nothing and answers200. - Group member removal works only with
path: "members". A path such asmembers[value eq "<id>"]removes nobody. /ServiceProviderConfiganswers inside a list envelope. RFC 7644 defines it as a single resource. We have not tested how a connector reads it.
Also not yet tested: a user that SCIM created and who then signs in through SSO. Do not rely on that flow.
Base URL and format
The SCIM base is /api/scim/v2 on the API origin. Your customer registers the full URL in their IdP. RealmSSO reads a request body sent as application/scim+json or application/json. Responses come back as application/json.
API=https://api.realmsso.com
curl -G -H "Authorization: Bearer <scim-token>" \
--data-urlencode 'filter=userName eq "ada@customer.example"' \
"$API/api/scim/v2/Users"The Admin Portal shows a SCIM URL built from its own address. We have not verified that this address reaches the SCIM API. Give your customer the URL on the API origin.
SCIM has no /ResourceTypes route, no Bulk, no sorting and no ETag. A resource id is the id of the RealmSSO directory row. A route that takes an :id also accepts the externalId.
Authentication
Every /Users and /Groups request must carry Authorization: Bearer <token>. The two discovery routes need no credential, because a connector reads them before it has a token. RealmSSO hashes the token and looks for an enabled SCIM configuration with that hash. A missing header, or a hash with no match, gets 401 in the SCIM error format.
Users
/api/scim/v2/UsersReturns a ListResponse with totalResults, startIndex, itemsPerPage and Resources. The query parameters are filter, startIndex and count. The default count is 100. RealmSSO caps count at 200.
Only this route filters. It reads two forms: userName eq "<value>" and email eq "<value>". It ignores any other filter and returns every user. GET /Groups ignores filter.
/api/scim/v2/UsersThe body holds userName (required), name, emails, active (default true), externalId and groups. The response is 201. A second POST with the same userName updates the same user and also answers 201.
/api/scim/v2/Users/:id/api/scim/v2/Users/:idReplaces the user.
/api/scim/v2/Users/:idTakes Operations, a list of { op, path, value }. RealmSSO acts on the paths userName, email, emails, name.givenName, name.familyName, externalId and active. Other paths change none of these fields.
/api/scim/v2/Users/:idRemoves the directory row and answers 204. See the provisioning table above.
Groups
/api/scim/v2/GroupsReturns a ListResponse with the members of each group.
/api/scim/v2/GroupsThe body holds displayName (required), members and externalId. The response is 201.
/api/scim/v2/Groups/:id/api/scim/v2/Groups/:idReplaces the name and the members of the group.
/api/scim/v2/Groups/:idRenames the group with displayName. Adds and removes members with path: "members".
/api/scim/v2/Groups/:idRemoves the directory row and answers 204.
Discovery
/api/scim/v2/ServiceProviderConfigNeeds no credential. It reports that patch and filter are supported, with maxResults 200. It reports that bulk, changePassword, sort and etag are not supported. The only authentication scheme is an OAuth bearer token. The response is a ListResponse with one resource.
/api/scim/v2/SchemasNeeds no credential. Returns the User and Group schema definitions.
Errors
SCIM answers 401, 403 and 404 in the SCIM error format. A body that fails validation answers 400 in the standard API format. See the API overview.
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"status": "404",
"detail": "User <id> not found"
}| Status | detail | When |
|---|---|---|
| 401 | Missing or invalid Authorization header, or Invalid SCIM token | No bearer token, or no enabled SCIM configuration matches it. |
| 403 | SCIM writes are disabled for this account, with the reason | A write for an account in a shared realm. |
| 404 | User <id> not found, or Group <id> not found | No directory row for the id in this account. |
Related pages