User provisioning (SCIM)
Let your identity provider manage who is in your organization. People assigned to LayerSift in Okta, Microsoft Entra ID, OneLogin or JumpCloud become members, renames follow them, and taking someone out of the app in the identity provider takes away their access here at once, sessions included.
This is SCIM 2.0 (RFC 7643, RFC 7644), Users only.
Base URL https://<your LayerSift host>/api/scim/v2
Authorization Bearer scim_live_…
Content-Type application/scim+json
Before you start: single sign-on with a proved domain
Provisioning builds on single sign-on: set up the connection under Settings → Single sign-on and prove your email domain with its DNS record first.
The reason is the rule single sign-on itself lives by. An identity provider can assert any address, so it is believed only for domains your organization has proved it owns. Provisioning follows that rule:
- On your verified domains it creates accounts and adds people. They sign in
with Sign in with single sign-on; the account it creates has a password
nobody knows (never an empty one), so single sign-on is its way in, and nobody
can sign up with that address in the meantime and take it.
An account that already exists on such an address, and whose address nobody has
proved, is claimed before it is linked. Sign-up never proves an address, so
someone may have registered the address with a password of their own — and
a verification link proves it only for someone signed in to the account it
was sent for, so the address's owner following one does not prove it for
them. A reset link the owner follows claims the account just as below (its
user.password_resetaudit entry sayssecondFactorsRemoved). The claim replaces that password with one nobody knows, removes its authenticator app, recovery codes and passkeys, and ends every session it had. The person then signs in with single sign-on. The claim is audited asaccount.claimed. A single sign-on sign-in that proves the address makes the same claim. An account whose address is already proved is linked as it is, with its password, second factors and sessions. Owners are claimed too: nothing an owner was given proves the address. Keep one owner on an address outside the domains single sign-on speaks for — no claim touches it, enforcement does not apply to it, and it is the way back in when the provider fails. - Anywhere else it only links people who are already members (a contractor you invited, say, who set their own password). Adding anyone else takes an invitation they accept. The refusal is the same whether or not an account exists for the address, so a token cannot be used to find out.
Create a token
Tokens are minted with a management key that holds
members:write, and shown once:
curl -X POST https://<host>/api/management/v1/scim-tokens \
-H "Authorization: Bearer mk_live_…" \
-H "Content-Type: application/json" \
-d '{ "name": "Okta" }'
{
"id": "sct_…",
"name": "Okta",
"token": "scim_live_…",
"scimBaseUrl": "https://<host>/api/scim/v2",
"createdAt": "2026-10-23T12:00:00.000Z",
"warning": "Store this value now — it is not retrievable again."
}
Name it after the identity provider that will hold it: every change the token makes is audited under that name. Only the token's SHA-256 is stored.
GET | POST /api/management/v1/scim-tokens members:read | members:write
GET | DELETE /api/management/v1/scim-tokens/{tokenId} members:read | members:write
GET lists them as scim_live_ab12cd…wxyz, revoked ones with
?includeRevoked=true. DELETE revokes: the provider's next request is 401,
and the people it provisioned stay as they are. To rotate, mint a new token, put
it in the provider, then revoke the old one. An organization holds at most ten
active tokens.
A token lives as long as the management key that minted it. Revoking that key,
in the dashboard or through the API, revokes its tokens in the same step, and so
does deleting the environment the key belongs to. The provider's next request is
401.
Configure your identity provider
Every provider asks for the same three things:
| SCIM base URL | https://<host>/api/scim/v2 (the scimBaseUrl above) |
| Authentication | HTTP header / Bearer token: the scim_live_… value |
| Unique identifier | userName |
Then turn on creating users, updating user attributes and deactivating users. There is nothing to configure for groups or passwords: neither is supported.
Okta. In the app's provisioning settings, choose SCIM 2.0 with HTTP Header
authentication and userName as the unique identifier; enable Push New Users,
Push Profile Updates and Deactivate Users (leave Push Groups and password
sync off — a password Okta sends is ignored). Okta deactivates by
PATCH {"op":"replace","value":{"active":false}}, which is supported as is.
Microsoft Entra ID. In the enterprise application's Provisioning, set the
mode to Automatic, the Tenant URL to the base URL and the Secret Token to
the token; Test Connection reads /Users. Keep the default user mappings
(userPrincipalName → userName, mail → emails[type eq "work"].value,
Switch([IsSoftDeleted]…) → active); the enterprise extension attributes it
sends are accepted and ignored. Turn group provisioning off. Entra ID sends
booleans as "True"/"False" strings and ops as "Replace": both are understood.
OneLogin, JumpCloud and other SCIM 2.0 clients: the same three settings,
SCIM 2.0 with a bearer token, userName as the identifier.
Turn on assignment required (Entra ID: Properties → Assignment required?) so the provider only signs in the people it provisions.
What each request does
A SCIM User is a person's membership of this organization. Its id is the
provisioning record's (scim_…), not the account's: one account can belong to
many organizations, and a provider speaks only for its own.
| Request | Effect |
|---|---|
POST /Users |
Finds the account by address, or creates it (verified domains only); makes them a member, or links the membership they already have. New members join as member — as viewer if single sign-on makes newcomers viewers. Never admin or owner. Pending invitations for the address are revoked. Counts against your plan's seats. |
GET /Users, GET /Users/{id} |
The people this organization's provider manages. |
PUT /Users/{id} |
Replaces the profile. Attributes left out are cleared, except active and emails, which stay as they are. |
PATCH /Users/{id} |
add, replace, remove, with or without a path; all operations or none. Without a path, the value may name attributes under the core User URN: {"urn:ietf:params:scim:schemas:core:2.0:User": {"active": false}}. |
active: false |
Deactivates (below). |
active: true |
Re-activates: the membership comes back with the role it had (at most member). |
DELETE /Users/{id} |
Removes the membership and ends their sessions here. The account is never deleted — it is the person's, and may belong to other organizations. |
Deactivation keeps the user resource but takes away access:
- the membership is removed — it is what every page, action and API call checks, so nothing of the organization is reachable from that moment;
- the sessions they had open on the organization end, and so do any sessions your single sign-on issued them, wherever they are now; sessions on other organizations they belong to are theirs to keep;
- pending invitations to the organization for their address are revoked;
- while they are deactivated nothing else can make them a member again — not a sign-in through single sign-on (which otherwise joins people on their first sign-in), not an invitation. Only re-activating them in the provider, or the provider deleting them, lifts that.
A request that deactivates always lands. If the same request also asks for a
change that cannot be made (a userName another person already has, or an
address that is not one), that change is left out and the answer is 200 with
the resource as stored. The audit entry names what was left out. Owners and
admins still cannot be deactivated (below).
A provider that sends active: true for someone an admin removed in the
dashboard brings them back: when provisioning is on, the identity provider is
the source of truth for who works here. Remove people in the provider.
What a token cannot do
A token acts with the authority of the management key that minted it — an admin's — and no more:
- Owners and admins can be linked and read, and the profile the provider
sends for them is recorded, but a token cannot deactivate or delete them (
403). Change their role or remove them in the dashboard. Like single sign-on's enforcement, which never shuts an owner out, this keeps a way back in on the day the provider is down or misconfigured. The organization's last owner is refused on its own as well (409). - Another organization's people: invisible. Their ids are
404, as for anything that does not exist. - Addresses. What the provider says a person's addresses are is recorded on
their SCIM resource and answered back as sent. It does not change the account:
the account keeps the address it signs in with, and single sign-on finds an
account by that address only. An address change at the provider therefore shows
in
GET /Users, and the person keeps signing in as before. A name follows the provider while the person's address there is the account's own and is on your verified domains; for linked people elsewhere the account's own name is left alone. - Passwords: never set, never changed.
Attributes
| SCIM | LayerSift |
|---|---|
id |
The provisioning record (scim_…) |
userName |
Unique in the organization, case-insensitively |
externalId |
The provider's id, case-exact |
name.givenName, familyName, formatted, middleName, honorificPrefix, honorificSuffix |
Kept on the record |
displayName |
Kept, and the account's name on your verified domains |
emails |
Kept as the provider sent them, and answered as they were sent. Until it sends some, the account's address, as one work email. The address the person signs in with is the account's own and does not change here |
active |
Whether they can use the organization |
meta.created, lastModified, location |
Other core attributes (title, phoneNumbers, addresses, locale,
password, groups, roles, …) and extension attributes are accepted and
ignored, so default mappings work unchanged. An attribute that does not exist
on a User at all is 400 invalidPath.
Lists and filters
GET /Users?filter=…&startIndex=1&count=100. Pages are 1-based, in creation
order; count is at most 200 (0 returns just totalResults). Resources is
always present, even when empty.
Filters support eq ne co sw ew gt ge lt le pr, and, or, not ( … ),
parentheses and value paths, on id, externalId, userName, displayName,
active, name.*, emails, emails.value/type/primary and meta.*:
userName eq "[email protected]"
emails[type eq "work"].value eq "[email protected]"
externalId eq "00u1abcd"
active eq false
userName sw "a" and not (externalId pr)
userName and email comparisons ignore case; id and externalId do not.
Anything else is 400 invalidFilter. Sorting, attributes and
excludedAttributes are not supported and are ignored.
A list without a filter is paged in the database, and totalResults is counted
there. A filter that requires userName, externalId or id to equal a value
(at its top level, joined by and to the rest) reads only the matching people,
so the lookup a provider makes before it creates someone stays quick however
large the organization is. The whole filter still decides what matches.
Discovery
GET /api/scim/v2/ServiceProviderConfig patch, filter (max 200); no bulk, sort, etag, password change
GET /api/scim/v2/ResourceTypes User
GET /api/scim/v2/Schemas the core User schema
All need the token. /Groups and anything else under the base URL answer a
SCIM 404 saying so; /Bulk and /Me are 501.
Errors
Every error is in SCIM's format, with a detail written for the admin reading
the provider's provisioning log:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"status": "400",
"scimType": "invalidFilter",
"detail": "Filtering on \"title\" is not supported."
}
| Status | When |
|---|---|
400 |
invalidSyntax (not JSON, no Operations), invalidValue, invalidFilter, invalidPath, noTarget, mutability |
401 |
No token, an unknown one, or a revoked one |
403 |
An address not on your verified domains; an owner or admin; no seats left; the organization already keeps 1,000 deactivated people (tooMany) |
404 |
No such user in this organization |
409 |
uniqueness (the userName, or the person, is already provisioned; an address taken); the last owner |
413 |
A body over 64 KB |
429 |
Over the rate limit, or more than 60 new people a minute for the organization (tooMany); honour Retry-After |
Limits
- 1,800 requests a minute per token — 30 a second, above the 25 Microsoft asks a SCIM service to sustain.
- Requests without a valid token are counted per source address and refused for a while after 30 in a minute.
- An organization can bring at most 60 new people into it a minute: a person
whose address has no account yet. Linking someone who already has an account is
not counted. Past that,
429withtooMany. - An organization keeps at most 1,000 deactivated people provisioned. Creating
another one as inactive — a person whose address has no account yet — is
403withtooMany, and the detail says so. Linking someone who already has an account as inactive is not refused: it makes no account, and there are only as many as people have made on your verified domains. - Bodies up to 64 KB.
Audit
Every change is in the audit log under scim:<token name>,
with the token's id in its metadata:
| Action | |
|---|---|
scim_token.created, scim_token.revoked |
By the management key, under key:<name> |
scim.user.created |
Says whether it linked an existing member and whether it created the account |
scim.user.updated |
Names the attributes that changed; a userName change with from and to; the addresses as sent, with from and to |
scim.user.deactivated |
With how many sessions it ended, and any change it left out (skipped) |
scim.user.reactivated |
With the role given back |
scim.user.deleted |
|
account.claimed |
An account nobody had proved, claimed when a provider linked it or single sign-on proved its address: its password, second factors and sessions were taken away. Names the organization and the reason |
Requests that change nothing — providers re-send state — are not audited.