Skip to content

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

On this page

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 add assigns the role and replaces the previous one. A remove resets to the base role. A PUT is 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 /Groups therefore matches an existing role by its displayName. An unknown name gets 400 with scimType: "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 /Users with the same address returns the same account, and a PATCH active:false on 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