Skip to content
← Documentation

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.

SwitchAPI fieldDefaultEffect
Auto-provisionautoProvisionOnA user push creates or updates the user in the realm.
Auto-deprovisionautoDeprovisionOffDELETE /Users/:id disables the user in the realm.
Group syncgroupSyncOffA group push creates, renames and deletes the group in the realm, and syncs its members.
Attribute mappingattributeMappingEmptyCopies 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

RequestDirectory rowUser in the realm, with auto-provision on (default)
POST /UsersWritten.Created or updated by userName. The user has no password. RealmSSO marks the email as verified.
PUT or PATCH with active: falseMarked inactive.Disabled. RealmSSO then asks the sign-in service to end the sessions of the user.
DELETE /Users/:idRemoved.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:

  • op must be lowercase: add, remove or replace. A capitalised Replace returns 400.
  • A replace with no path and { "active": false } as the value is ignored. RealmSSO changes nothing and answers 200.
  • Group member removal works only with path: "members". A path such as members[value eq "<id>"] removes nobody.
  • /ServiceProviderConfig answers 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

GET/api/scim/v2/Users

Returns 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.

POST/api/scim/v2/Users

The 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.

GET/api/scim/v2/Users/:id
PUT/api/scim/v2/Users/:id

Replaces the user.

PATCH/api/scim/v2/Users/:id

Takes 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.

DELETE/api/scim/v2/Users/:id

Removes the directory row and answers 204. See the provisioning table above.

Groups

GET/api/scim/v2/Groups

Returns a ListResponse with the members of each group.

POST/api/scim/v2/Groups

The body holds displayName (required), members and externalId. The response is 201.

GET/api/scim/v2/Groups/:id
PUT/api/scim/v2/Groups/:id

Replaces the name and the members of the group.

PATCH/api/scim/v2/Groups/:id

Renames the group with displayName. Adds and removes members with path: "members".

DELETE/api/scim/v2/Groups/:id

Removes the directory row and answers 204.

Discovery

GET/api/scim/v2/ServiceProviderConfig

Needs 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.

GET/api/scim/v2/Schemas

Needs 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"
}
StatusdetailWhen
401Missing or invalid Authorization header, or Invalid SCIM tokenNo bearer token, or no enabled SCIM configuration matches it.
403SCIM writes are disabled for this account, with the reasonA write for an account in a shared realm.
404User <id> not found, or Group <id> not foundNo directory row for the id in this account.

Related pages

Admin Portal explains how an IT admin turns SCIM on. Accounts API explains the account and its realm.