Provisioning with SCIM 2.0
A directory service should create accounts, suspend them, and set roles: token sct_, endpoints under /scim/v2, mapping, and errors.
5 minute readAPIUpdated: 10/25/2026
Before you start
- For
- Anyone who connects a directory service: Microsoft Entra ID, Okta, or a custom integration per RFC 7643 and RFC 7644.
- Prerequisite
- an active sign-in method through the organization’s provider and a SCIM token
sct_…from the portal
By the end of this page, a directory service connects the accounts of an organization to Szenera: Joining creates an account, leaving suspends it, and a group change sets the role. You learn what the token looks like, which endpoints exist, what is mapped to what, and what happens on conflicts.
The SCIM token
The interface lives under /scim/v2 and not under /v1: The path belongs to a third-party protocol, and a directory service validates its own data against the address it talks to.
| What | Value |
|---|---|
| Base URL | $SZENERA_API/scim/v2 |
| Credential | Authorization: Bearer sct_… |
| Content type | application/scim+json in both directions |
| Reach | exactly one organization per token |
If your environment has a sign-in host, the portal will in future name https://auth.<your-domain>/scim/v2 as the base URL, and exactly this is what appears in meta.location of the responses. New setups enter it from the start, and an existing one at the API base URL remains reachable until further notice.
The token is created in the portal under Settings › Organization › Company sign-in, section Provisioning (SCIM). Its value appears there exactly once. It is accepted on no other route of the platform: Everywhere else it is rejected with 403, and conversely no API key reaches /scim/v2.
The sign-in method is a condition, not a recommendation. An account created this way gets no password and signs in through the organization’s provider. Without an active provider, no token can be issued.
The endpoints
| Route | Methods |
|---|---|
/scim/v2/ServiceProviderConfig |
GET, feature set: partial update yes, filter yes, bulk no, sorting no |
/scim/v2/ResourceTypes, /scim/v2/ResourceTypes/{id} |
GET |
/scim/v2/Schemas, /scim/v2/Schemas/{id} |
GET |
/scim/v2/Users |
GET (list and filter), POST |
/scim/v2/Users/{id} |
GET, PUT, PATCH, DELETE |
/scim/v2/Groups |
GET (list and filter), POST |
/scim/v2/Groups/{id} |
GET, PUT, PATCH, DELETE (rejected, see below) |
A directory service starts with the first row:
curl -sS "$SZENERA_API/scim/v2/ServiceProviderConfig" \
-H "Authorization: Bearer $SZENERA_SCIM_TOKEN"
Create an account. This is repeatable, and the same address returns the same account:
curl -sS -X POST "$SZENERA_API/scim/v2/Users" \
-H "Authorization: Bearer $SZENERA_SCIM_TOKEN" \
-H "Content-Type: application/scim+json" \
-d '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "deniz.acar@demo.local",
"externalId": "entra-8f3c1a20",
"name": { "givenName": "Deniz", "familyName": "Acar" },
"active": true
}'
Suspend an account. A partial update is the standard way:
curl -sS -X PATCH "$SZENERA_API/scim/v2/Users/usr_01KFB7QPW0TEAM000000000002" \
-H "Authorization: Bearer $SZENERA_SCIM_TOKEN" \
-H "Content-Type: application/scim+json" \
-d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [{ "op": "replace", "path": "active", "value": false }]
}'
Paging and filtering
startIndex counts from one, and count is capped at 200. Exactly the form attribute eq "value" is accepted as a filter: userName eq and externalId eq on Users, displayName eq on Groups. Everything else is 400 with scimType: "invalidFilter", because a silently ignored filter would return the entire set of accounts.
curl -sS --get "$SZENERA_API/scim/v2/Users" \
-H "Authorization: Bearer $SZENERA_SCIM_TOKEN" \
--data-urlencode 'filter=userName eq "deniz.acar@demo.local"'
The mapping
| SCIM | Szenera |
|---|---|
userName |
the email address. It is unique per organization and therefore the shared key |
externalId |
is stored, is unique per organization, and can be found via externalId eq |
name.givenName, name.familyName, displayName |
the display name of the profile |
active: false |
suspend the account. active: true reactivates it |
DELETE /Users/{id} |
suspends, does not delete |
Group |
a role of the organization. displayName is its name |
members |
the accounts that hold this role |
Fields that Szenera does not keep (title, phoneNumbers, extension schemas) are silently discarded. Your mapping in the directory service does not need to be thinned out for that.
Four peculiarities that every integration must know about:
- Nothing is ever deleted. Audit log entries, uploaded media, and created programs hang off an account. Permanent deletion remains a deliberate step by a person in the portal.
- An account holds exactly one role, never none. An
addassigns the role and replaces the previous one. Aremoveresets to the base role. APUTis both together, because anyone who is missing from the list falls back. - Groups are not created through SCIM. The substance of a role is its permissions, and the protocol does not carry them.
POST /Groupstherefore matches an existing role by itsdisplayName. An unknown name gets400withscimType: "invalidValue". Create the roles in the portal first and name the groups in the directory identically. - Every change is repeatable. A directory service retries after every timeout, even if the first attempt went through. A second
POST /Userswith the same address returns the same account, and aPATCH active:falseon an already suspended account is a silent success.
Every change writes an entry into the audit log of the organization, and the originator shown there is “SCIM”.
Errors
Under /scim/v2, the error format of the protocol applies, not that of this API: A directory service evaluates scimType programmatically and decides accordingly whether to retry, skip the entry, or stop.
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"status": "409",
"scimType": "uniqueness",
"detail": "A user with this userName already exists in this organization"
}
status is a string, as the protocol prescribes.
| Case | Response |
|---|---|
| The credential is missing or invalid | 401 |
The credential is something other than sct_… |
403 |
| The address already exists in this organization | 409 with scimType: "uniqueness" |
The filter has a form other than attribute eq "value" |
400 with scimType: "invalidFilter" |
POST /Groups names a role that does not exist |
400 with scimType: "invalidValue" |
DELETE /Groups/{id} |
400 with scimType: "mutability". Roles are deleted in the portal |