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).
Scopes
Section titled “Scopes”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.
Authentication
Section titled “Authentication”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+jsonAccept: application/scim+jsonConventions
Section titled “Conventions”-
id— the stable Lavanda identifier (a UUID) returned as the top-levelidof the resource (meta.locationis the resource URL, which ends in the same id). Store it as the IdP’s external id for the resource.userName(Users) anddisplayName(Groups) may also be used to correlate. -
Pagination — list endpoints accept
startIndex(1-based, default1) andcount(default100, maximum200). Responses are SCIMListResponseenvelopes withtotalResults,startIndexanditemsPerPage. -
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, …) returns400withscimType: invalidFilter. - Users:
-
active— a user’sactiveflag reflects whether they have an active membership of the path workspace, not a global state. Deprovisioning sets it tofalse; 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. |
Provision a user
Section titled “Provision a user”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" }}Deprovision a user
Section titled “Deprovision a user”Either DELETE the user or PATCH active to false — both deactivate the user’s membership of the workspace.
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 }] }'Groups
Section titled “Groups”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. |
Add and remove members
Section titled “Add and remove members”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).
Organization scope
Section titled “Organization scope”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}Organization users
Section titled “Organization users”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.
Organization groups
Section titled “Organization groups”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 LettingsOperations Manager — Division: LondonHousekeeping — Portfolio: North LondonAnalytics Viewer — Workspace: Camden HouseThe Group id is an opaque, stable identifier returned on every Group; store it and use it for subsequent
GET / PUT / PATCH / DELETE calls.
GET /Groupslists 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 adisplayName eq "Role — Level: Name"filter; a name that matches nothing returns an empty list (200with no results), not an error.POST /Groupsresolves the supplieddisplayNameto 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,PATCHandDELETEbehave as in the workspace scope:PUTreconciles the exact member set,PATCHadds/removes members, andDELETErevokes the role from all its current members.
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>" }] }'Example: grant a role at a portfolio
Section titled “Example: grant a role at a portfolio”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):
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):
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:
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 /Groupswith thedisplayNameand amembersarray grants the role to those members in one call.
Errors
Section titled “Errors”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. |