SCIM 2.0 reference
The SCIM provisioning surface for Enterprise workspaces. Endpoints, authentication, filter support, PATCH operations, and the error schema.
Fermium implements the subset of SCIM 2.0 (RFC 7643 and RFC 7644) that Microsoft Entra ID, Okta and similar provisioning clients use: Users keyed by userName, Groups with members, eq filters, and PATCH. SCIM is an Enterprise feature. The user-facing setup is in SSO and SCIM.
Base URL and authentication
https://fermium.app/scim/v2
Authorization: Bearer fsc_…
Tokens are minted by a workspace owner or admin with POST /api/scim/tokens (see below), shown once, and stored hashed. Every request carries one. A missing or invalid token gets a 401 with WWW-Authenticate: Bearer and a SCIM error body. A token of a workspace that is no longer on Enterprise stops working.
Every response, including errors, is application/scim+json.
Token management (app session)
These are ordinary /api endpoints authenticated with the app session, for the workspace settings page and scripts.
| Method and path | Body | Returns |
|---|---|---|
GET /api/scim/tokens |
{tokens: [{id, label, created_at, last_used_at, revoked}], available, plan, base_url} |
|
POST /api/scim/tokens |
{"label": "Entra"} (optional) |
201 with the token row plus token (once) and base_url; 402 below Enterprise |
DELETE /api/scim/tokens/{id} |
{"revoked": id} |
curl -b "$COOKIE" -H "Origin: https://fermium.app" -H "Content-Type: application/json" \
-d '{"label":"Okta"}' https://fermium.app/api/scim/tokens
Minting and revoking are audited as scim.token_create and scim.token_revoke.
Discovery
| Path | Notes |
|---|---|
GET /scim/v2/ServiceProviderConfig |
patch.supported: true, filter.supported: true with maxResults: 200, bulk, sort, etag and changePassword not supported, bearer token authentication |
GET /scim/v2/ResourceTypes |
User and Group |
GET /scim/v2/Schemas |
the core User schema (userName, externalId, displayName, name, emails, active) and Group schema (displayName, externalId, members) |
Users
urn:ietf:params:scim:schemas:core:2.0:User. A user resource:
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "42",
"externalId": "9b1c…",
"userName": "bo@acme.com",
"name": { "givenName": "Bo", "familyName": "", "formatted": "Bo" },
"displayName": "Bo",
"emails": [{ "value": "bo@acme.com", "primary": true, "type": "work" }],
"active": true,
"meta": { "resourceType": "User", "created": "2026-09-15T10:00:00Z", "lastModified": "2026-09-15T10:00:00Z", "location": "https://fermium.app/scim/v2/Users/42" }
}
id is Fermium's user id. userName must be an email address; it is the account's email. name is derived from the email and returned for compatibility; names, phone numbers, titles and the enterprise extension are accepted in requests and ignored.
| Method and path | Behaviour |
|---|---|
GET /scim/v2/Users |
list, with filter, startIndex (1-based) and count (default 100, at most 200) |
GET /scim/v2/Users/{id} |
by Fermium id or by externalId; a deactivated user remains readable so the client can reactivate them |
POST /scim/v2/Users |
create; 201. A user who already exists in the workspace and is active is a 409 uniqueness. A deactivated or elsewhere-provisioned user with the same email is adopted into the workspace instead |
PUT /scim/v2/Users/{id} |
replace userName, externalId and active |
PATCH /scim/v2/Users/{id} |
apply operations (below) |
DELETE /scim/v2/Users/{id} |
deprovision: same as active: false; 204 |
Created users get the member role and count against the plan's seat cap; a full workspace is a 409 with a detail naming the plan and the cap.
Deactivation (active: false, or DELETE) disables the account, removes it from the workspace's groups, and moves it to a personal workspace of its own. The seat is freed. The person's documents are kept under their account and are not deleted. Reactivation (active: true) re-enables the account and moves it back into the workspace if a seat is free.
Filters
One term of the form attribute op "value". Supported attributes and operators:
| Resource | Attributes | Operators |
|---|---|---|
| Users | userName, emails, emails.value, externalId, active |
eq, ne, co, sw, ew, pr; active takes eq true or eq false |
| Groups | displayName, externalId |
eq, ne, co, sw, ew, pr |
Text comparison is case-insensitive. A schema URN prefix on the attribute is accepted and stripped. Anything else (and, or, brackets, other attributes) is a 400 with scimType: invalidFilter.
curl -H "Authorization: Bearer $SCIM_TOKEN" \
'https://fermium.app/scim/v2/Users?filter=userName%20eq%20%22bo%40acme.com%22'
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": 1,
"startIndex": 1,
"itemsPerPage": 1,
"Resources": [ { "…": "…" } ]
}
PATCH operations
urn:ietf:params:scim:api:messages:2.0:PatchOp with an Operations array. op is add, replace or remove; other values are a 400 invalidSyntax.
For users:
| Path | Effect |
|---|---|
active |
activate or deactivate (see above) |
userName, emails.value, emails[type eq "work"].value |
change the email; 409 uniqueness if another account has it |
externalId |
set, or clear with remove |
no path, object value (Okta style) |
each key is applied as above |
| anything else | accepted and ignored |
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [{ "op": "replace", "path": "active", "value": false }]
}
Groups
urn:ietf:params:scim:schemas:core:2.0:Group. A group resource:
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"id": "5",
"externalId": "e7a1…",
"displayName": "Design",
"members": [{ "value": "42", "display": "bo@acme.com", "$ref": "https://fermium.app/scim/v2/Users/42" }],
"meta": { "resourceType": "Group", "created": "2026-09-15T10:00:00Z", "lastModified": "2026-09-15T10:00:00Z", "location": "https://fermium.app/scim/v2/Groups/5" }
}
| Method and path | Behaviour |
|---|---|
GET /scim/v2/Groups |
list, with filter, startIndex, count |
GET /scim/v2/Groups/{id} |
by Fermium id or externalId |
POST /scim/v2/Groups |
create with displayName, optional externalId and members; 409 uniqueness if a group with that name exists |
PUT /scim/v2/Groups/{id} |
replace name, external id and, when members is present, the whole membership |
PATCH /scim/v2/Groups/{id} |
operations below |
DELETE /scim/v2/Groups/{id} |
remove the group and every access grant that named it; 204 |
Groups created this way appear in Fermium with source: scim and a slug derived from the name, and work in access grants like groups made by hand. Members are referenced by Fermium user id, by externalId, or by email; a reference that is not a user in this workspace is a 400 invalidValue.
Group PATCH paths:
| Path | Effect |
|---|---|
members with add |
add the listed users |
members with remove and a value list, or members[value eq "42"] with remove |
remove those users |
members with replace |
set the membership to the list |
displayName |
rename |
externalId |
set or clear |
Errors
Every error is:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"status": "409",
"scimType": "uniqueness",
"detail": "a user with userName 'bo@acme.com' already exists"
}
| Status | scimType |
When |
|---|---|---|
| 400 | invalidSyntax |
body is not a JSON object, or an unknown PATCH op |
| 400 | invalidValue |
userName is not an email, a PATCH value has the wrong shape, an unknown group member |
| 400 | invalidFilter |
a filter outside the supported subset |
| 401 | missing or invalid bearer token, or the workspace is not on Enterprise | |
| 404 | unknown user or group id | |
| 409 | uniqueness |
duplicate userName or group name, or the workspace is at its member cap |
Audit
Provisioning writes to the workspace audit log with the actor scim:<workspace-slug>: member.join and member.leave with via: scim, group.create and group.delete with via: scim.
Not supported
Bulk operations, sorting, ETags, password management, and the /Me endpoint. Filters with more than one term. Attributes other than those listed are accepted and ignored, as RFC 7643 allows.