Skip to content

SCIM provisioning

Requires a license with the scim feature. See pricing.

SCIM provisioning lets your identity provider manage LyEve users for you. When someone joins, changes or leaves in Azure AD, Okta or another SCIM 2.0 directory, the directory calls LyEve and the change arrives without anyone opening the admin console. SCIM signs nobody in. Pair it with SAML single sign-on, so the people your directory provisions are the people who can sign in.

PartWhat it is
ConnectionA name, a bearer token and what a delete does. A super admin creates it in LyEve.
SCIM base URLhttps://<your instance>/api/v1/scim/v2 on the Content API. The directory calls it with the token.
AccountA provisioned user gets a LyEve account at their first SAML sign-in through the linked provider, with editor when no other role is given.
GroupStored with its members, so the directory can read it back. A group does not grant a role in LyEve.

What you can rely on:

  • Each connection authenticates with its own bearer token, and sees only the users and groups it created.
  • A connection belongs to the tenant it was created in, and everything it provisions lands in that tenant.
  • LyEve stores a hash of each token, never the token.
  • A SCIM password attribute is never stored.
  1. Run with a license that carries scim. See licensing and tiers.
  2. Restart the instance.
  3. Create a connection. A super admin opens Access > SCIM provisioning in the admin console and chooses New connection, or uses the API below.
  4. In your identity provider, set the SCIM base URL to https://lyeve.example.com/api/v1/scim/v2 and the token to the one you chose.

You need a super admin token in TOKEN. The quickstart shows how to get one.

  1. Create a connection with a long random token:

    Terminal window
    SCIM_TOKEN=$(openssl rand -hex 32)
    curl -X POST http://localhost:3001/api/admin/scim/providers \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"name": "azure-ad", "bearer_token": "'"$SCIM_TOKEN"'"}'
    {
    "bearer_token": "4dc4c30c8bb6...",
    "provider": {
    "id": "47fe92cc-de5c-4c56-9b56-4e45fea8061c",
    "name": "azure-ad",
    "enabled": true,
    "tenant_id": "default",
    "attribute_map": { "userName": "userName", "displayName": "displayName", "externalId": "externalId", "active": "active", "groups": "groups" },
    "deprovision_on_delete": true,
    "deprovision_action": "disable",
    "created_at": "2026-10-01T09:30:00Z",
    "updated_at": "2026-10-01T09:30:00Z"
    }
    }

    The answer is 201. This is the only time the token is returned. Copy provider.id into PROVIDER_ID.

  2. Read what the server supports. Discovery needs no token:

    Terminal window
    curl http://localhost:3002/api/v1/scim/v2/ServiceProviderConfig

    The answer uses application/scim+json and says patch and filters are supported, with at most 200 results, and bulk, sort and password changes are not.

  3. Create a user the way a directory does:

    Terminal window
    curl -X POST http://localhost:3002/api/v1/scim/v2/Users \
    -H "Authorization: Bearer $SCIM_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
    "userName": "ada@example.com",
    "active": true,
    "emails": [{"value": "ada@example.com", "primary": true}]
    }'

    The answer is 201 with the SCIM user, its id and meta.

  4. Find the user by name:

    Terminal window
    curl "http://localhost:3002/api/v1/scim/v2/Users?filter=userName%20eq%20%22ada%40example.com%22" \
    -H "Authorization: Bearer $SCIM_TOKEN"
    {
    "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
    "totalResults": 1,
    "startIndex": 1,
    "itemsPerPage": 100,
    "Resources": [
    {
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
    "id": "2ed58a06-1876-45d8-8eaf-a280ad86c9e3",
    "userName": "ada@example.com",
    "active": true
    }
    ]
    }
  5. Call with the wrong token to see the refusal:

    Terminal window
    curl http://localhost:3002/api/v1/scim/v2/Users \
    -H "Authorization: Bearer wrong"
    { "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], "detail": "invalid provider credentials", "status": 401, "scimType": "sensitive" }
FieldMeaningDefault
nameA name for the connectionrequired
bearer_tokenThe token the directory sends as Authorization: Bearer <token>required
saml_provider_idThe SAML provider the same directory signs users in throughnone
attribute_mapWhich SCIM attributes feed userName, displayName, externalId, active and groupseach attribute maps to itself
deprovision_actionWhat a SCIM delete does: disable marks the user inactive and is meant to clear the account's roles (see the caution above), delete removes the LyEve account. Any value other than disable deletesdisable

With saml_provider_id set, the first SAML sign-in of a provisioned user creates their LyEve account.

Terminal window
curl -X POST http://localhost:3001/api/admin/scim/providers/$PROVIDER_ID/rotate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"new_token": "'"$(openssl rand -hex 32)"'"}'

The answer is {"bearer_token": "<the new token>"}. The old token stops working at once, so update the identity provider right after.

The directory authenticates every call except discovery with its bearer token. Responses use the application/scim+json content type and the SCIM 2.0 shapes. Lists take filter, startIndex and count. count defaults to 100 and is capped at 200. Filters accept eq, ne, co, sw, ew, gt, ge, lt, le and pr.

SCIM routes
MethodPathPurpose
GET/api/v1/scim/v2/ServiceProviderConfigWhat this server supports. No token
GET/api/v1/scim/v2/SchemasThe resource schemas. No token
GET/api/v1/scim/v2/ResourceTypesThe resource types. No token
GET/api/v1/scim/v2/UsersList users
POST/api/v1/scim/v2/UsersCreate a user. 201
GET/api/v1/scim/v2/Users/{id}Read a user
PUT/api/v1/scim/v2/Users/{id}Replace a user
PATCH/api/v1/scim/v2/Users/{id}Change part of a user
DELETE/api/v1/scim/v2/Users/{id}Remove a user. 204
GET/api/v1/scim/v2/GroupsList groups
POST/api/v1/scim/v2/GroupsCreate a group
GET/api/v1/scim/v2/Groups/{id}Read a group
PUT/api/v1/scim/v2/Groups/{id}Replace a group
PATCH/api/v1/scim/v2/Groups/{id}Add or remove members
DELETE/api/v1/scim/v2/Groups/{id}Delete a group

These routes take a signed-in session. An admin token is refused on them with 403.

Connection routes
MethodPathRolePurpose
GET/api/admin/scim/providersadminList connections, paginated
POST/api/admin/scim/providerssuper_adminCreate a connection. 201
GET/api/admin/scim/providers/{id}adminRead a connection
DELETE/api/admin/scim/providers/{id}super_adminRemove a connection. 204
POST/api/admin/scim/providers/{id}/rotatesuper_adminReplace the token

If you set LYEVE_PLUGINS, include scim in it.

Errors on the SCIM routes come back in the SCIM error shape, with status and detail. The 415 comes back as {"error": "..."}, because it is refused before SCIM reads the request.

StatusMessageCause
401invalid provider credentialsThe directory sent no token or an unknown one.
404user not foundThe user does not exist, or another connection created it.
415unsupported media type, expected application/json or multipart/form-dataThe body was sent as application/scim+json or another type.
Errors from the connection routes
StatusMessageCause
400name is required or bearer_token is requiredA connection field is missing.
400new_token is requiredA rotation had no new token.
400invalid saml_provider_idThe id is not a UUID.
404provider not foundNo connection with that id in the tenant.
404The requested endpoint does not exist.The instance started without a license that carries scim.
402payment_requiredThe license stopped carrying scim while the instance was running.