Skip to content

SCIM 2.0

Lavanda exposes a SCIM 2.0 API for automated user and group provisioning from your identity provider (IdP). It implements the Users and Groups resources defined in RFC 7643 and RFC 7644, and is verified against Microsoft Entra ID, Okta and OneLogin.

Use SCIM to:

  • Provision users into a Lavanda workspace or organization when they are assigned the Lavanda app in your IdP.
  • Deprovision (deactivate) users when they leave or lose access.
  • Manage roles by adding and removing users from Groups (a Group maps to a Lavanda role).

SCIM is available at two scopes. Pick the one that matches how you want to manage access.

  • Workspace scope — one IdP connection provisions people and roles into a single workspace.

    https://platapi.lavanda.app/scim/v2/workspaces/{workspaceId}
  • Organization scope — one IdP connection covers a whole organization: its members, and roles granted at any level of the organization tree (organization, division, portfolio) or at an individual workspace beneath it.

    https://platapi.lavanda.app/scim/v2/organizations/{organizationId}

Replace {workspaceId} / {organizationId} with the id of the workspace or organization you are provisioning into. The Users and Groups resources below apply to both scopes; where the organization scope differs, it is called out in Organization scope.

SCIM requests use the same bearer token as the rest of the Lavanda API. Obtain a token with your client_id / client_secret via the OAuth2 client-credentials flow — see Auth — and send it on every request:

Authorization: Bearer <access_token>

If your IdP supports it, configure the SCIM connector with this token as a long-lived secret (refresh it before expiry). All requests must also set the SCIM media type:

Content-Type: application/scim+json
Accept: application/scim+json
  • id — the stable Lavanda identifier (a UUID) returned as the top-level id of the resource (meta.location is the resource URL, which ends in the same id). Store it as the IdP’s external id for the resource. userName (Users) and displayName (Groups) may also be used to correlate.

  • Pagination — list endpoints accept startIndex (1-based, default 1) and count (default 100, maximum 200). Responses are SCIM ListResponse envelopes with totalResults, startIndex and itemsPerPage.

  • Filtering — only equality filters are supported, matching what the certified IdPs emit:

    • Users: userName eq "alice@example.com"
    • Groups: displayName eq "Housekeeping"

    Any other filter (and/or, co, sw, pr, …) returns 400 with scimType: invalidFilter.

  • active — a user’s active flag reflects whether they have an active membership of the path workspace, not a global state. Deprovisioning sets it to false; it does not hard-delete the record.


Method Path Description
GET /Users List users in the workspace (paginated, filterable by userName).
POST /Users Provision a user.
GET /Users/{id} Fetch a single user.
PUT /Users/{id} Replace a user’s attributes.
PATCH /Users/{id} Partially update a user (the dominant flow is active → false deprovisioning).
DELETE /Users/{id} Deprovision (deactivate) a user.
Terminal window
curl --location 'https://platapi.lavanda.app/scim/v2/workspaces/{workspaceId}/Users' \
--header 'Authorization: Bearer <access_token>' \
--header 'Content-Type: application/scim+json' \
--header 'Accept: application/scim+json' \
--data '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "alice@example.com",
"name": { "givenName": "Alice", "familyName": "Smith" },
"displayName": "Alice Smith",
"emails": [{ "value": "alice@example.com", "primary": true }],
"active": true
}'

A successful create returns 201 Created with a Location header and the user representation:

{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "4d31fff1-5f12-4020-9926-589c46d9e8ac",
"userName": "alice@example.com",
"name": { "formatted": "Alice Smith", "givenName": "Alice", "familyName": "Smith" },
"displayName": "Alice Smith",
"emails": [{ "value": "alice@example.com", "primary": true }],
"active": true,
"meta": {
"resourceType": "User",
"created": "2026-06-23T10:19:43Z",
"lastModified": "2026-06-23T10:19:44Z",
"location": "https://platapi.lavanda.app/scim/v2/workspaces/{workspaceId}/Users/4d31fff1-5f12-4020-9926-589c46d9e8ac"
}
}

Either DELETE the user or PATCH active to false — both deactivate the user’s membership of the workspace.

Terminal window
curl --location --request PATCH 'https://platapi.lavanda.app/scim/v2/workspaces/{workspaceId}/Users/{id}' \
--header 'Authorization: Bearer <access_token>' \
--header 'Content-Type: application/scim+json' \
--data '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [{ "op": "replace", "path": "active", "value": false }]
}'

A SCIM Group maps to a Lavanda role. Adding a user to a Group assigns that role to the user within the path workspace; removing them revokes it.

Method Path Description
GET /Groups List groups (roles) in the workspace (filterable by displayName).
POST /Groups Create a group.
GET /Groups/{id} Fetch a single group and its members.
PUT /Groups/{id} Replace a group’s membership.
PATCH /Groups/{id} Add or remove members.
DELETE /Groups/{id} Delete the group.
Terminal window
curl --location --request PATCH 'https://platapi.lavanda.app/scim/v2/workspaces/{workspaceId}/Groups/{id}' \
--header 'Authorization: Bearer <access_token>' \
--header 'Content-Type: application/scim+json' \
--data '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "add", "path": "members", "value": [{ "value": "<user-id>" }] },
{ "op": "remove", "path": "members[value eq \"<user-id>\"]" }
]
}'

add, remove and replace operations on members are supported (the no-path Entra form is also accepted).


Everything above works the same at the organization scope, with two differences: a user is provisioned into the organization rather than a single workspace, and a Group represents a role at a specific place in the organization tree rather than a plain workspace role.

Use the organization base URL:

https://platapi.lavanda.app/scim/v2/organizations/{organizationId}

POST /Users adds the user to the organization; DELETE / PATCH active: false removes them from it. A user’s active flag reflects their organization membership. All other Users behaviour (idempotent create, pending invitation, soft deactivation, userName eq filtering) is unchanged.

At this scope a Group represents one role granted at one place in the organization — a node in the tree (organization, division or portfolio) or an individual workspace beneath it. Adding a user to the Group grants that role at that place; because roles inherit downward, a role granted at a division applies to every portfolio and workspace under it.

A Group’s displayName encodes both the role and the place, in the form Role — Level: Name, for example:

Admin — Organization: Acme Lettings
Operations Manager — Division: London
Housekeeping — Portfolio: North London
Analytics Viewer — Workspace: Camden House

The Group id is an opaque, stable identifier returned on every Group; store it and use it for subsequent GET / PUT / PATCH / DELETE calls.

  • GET /Groups lists the role-at-place combinations that currently have at least one member you can manage. To reference a combination that has no members yet (so your IdP can map to it before the first assignment), look it up by name with a displayName eq "Role — Level: Name" filter; a name that matches nothing returns an empty list (200 with no results), not an error.
  • POST /Groups resolves the supplied displayName to an existing role and place — neither the role nor the node is created by SCIM. The members you include are granted the role there.
  • GET /Groups/{id}, PUT, PATCH and DELETE behave as in the workspace scope: PUT reconciles the exact member set, PATCH adds/removes members, and DELETE revokes the role from all its current members.
Terminal window
curl --location 'https://platapi.lavanda.app/scim/v2/organizations/{organizationId}/Groups' \
--header 'Authorization: Bearer <access_token>' \
--header 'Content-Type: application/scim+json' \
--header 'Accept: application/scim+json' \
--data '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"displayName": "Housekeeping — Portfolio: North London",
"members": [{ "value": "<user-id>" }]
}'

Suppose you want a user to be a Housekeeper for the “North London” portfolio. That is the group Housekeeping — Portfolio: North London. Provision the user into the organization, then add them to that group.

1. Provision the user (skip if they are already an organization member):

Terminal window
curl --location 'https://platapi.lavanda.app/scim/v2/organizations/{organizationId}/Users' \
--header 'Authorization: Bearer <access_token>' \
--header 'Content-Type: application/scim+json' \
--header 'Accept: application/scim+json' \
--data '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "sam@example.com",
"displayName": "Sam Cleaner",
"active": true
}'

Note the returned id (the user’s SCIM id).

2. Find the group by displayName (this resolves even if the group has no members yet):

Terminal window
curl --location --get 'https://platapi.lavanda.app/scim/v2/organizations/{organizationId}/Groups' \
--header 'Authorization: Bearer <access_token>' \
--header 'Accept: application/scim+json' \
--data-urlencode 'filter=displayName eq "Housekeeping — Portfolio: North London"'

The ListResponse contains one Group; note its id.

3. Add the user to the group — this grants Housekeeping at the portfolio:

Terminal window
curl --location --request PATCH 'https://platapi.lavanda.app/scim/v2/organizations/{organizationId}/Groups/{groupId}' \
--header 'Authorization: Bearer <access_token>' \
--header 'Content-Type: application/scim+json' \
--data '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [{ "op": "add", "path": "members", "value": [{ "value": "<user-id>" }] }]
}'

GET /Groups/{groupId} now lists the user as a member. Removing them (a remove PATCH, a PUT that omits them, or DELETE on the group) revokes the role. Because roles inherit downward, this Housekeeping grant applies to every workspace under the North London portfolio.

Tip: Steps 2 and 3 can be combined: POST /Groups with the displayName and a members array grants the role to those members in one call.


Errors are returned as the SCIM error envelope:

{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"status": "400",
"detail": "..."
}
Status When
400 Malformed body, or an unsupported filter (scimType: invalidFilter).
401 Missing/invalid token, or a token that is not a partner API client.
403 Attempt to provision or modify a protected Lavanda-internal account.
404 Unknown resource id within the workspace or organization.