SCIM API
Identity providers call these endpoints for you; most administrators only need SCIM provisioning. This page is for people connecting an identity provider (IdP) that has no ready-made SCIM connector, and for security reviews.
There are two sets of endpoints:
/scim/v2/…, which the IdP calls with a SCIM bearer token. They follow SCIM 2.0 (RFC 7643 and RFC 7644)./api/v1/settings/sso/scim…, which a signed-in administrator uses to create and revoke those tokens and to say whether SCIM may give privileged roles.
Before you start
Section titled “Before you start”| Base URL | Your SpeculaPro address followed by /scim/v2. GET /api/v1/settings/sso/scim returns it as base_url. Self-hosted: it is built from SP_SSO_PUBLIC_URL |
| Authentication | Authorization: Bearer <token>. A SCIM token works only here: it is not a session and not an API key, and no cookie is read |
| Scope | A token belongs to one workspace. Other workspaces’ people and groups don’t exist as far as it can tell |
| Content type | Answers are application/scim+json |
| Rate limits | About 3,000 requests a minute per token. Thirty wrong tokens a minute from one address. Beyond either: 429 with Retry-After: 30 |
A token looks like scim_ followed by 12 characters, an underscore and the secret. The first part
(up to the second underscore) is the prefix shown in the app.
Endpoints at a glance
Section titled “Endpoints at a glance”Discovery
| Endpoint | Returns |
|---|---|
GET /scim/v2/ServiceProviderConfig |
What is supported: PATCH and filters yes (at most 200 results); bulk, password change, sort and ETags no |
GET /scim/v2/ResourceTypes, GET /scim/v2/ResourceTypes/{id} |
User and Group |
GET /scim/v2/Schemas, GET /scim/v2/Schemas/{id} |
The attributes kept for each |
People
| Endpoint | Does | Success |
|---|---|---|
GET /scim/v2/Users |
List people, optionally filtered | 200 |
POST /scim/v2/Users |
Create an account | 201 with a Location header |
GET /scim/v2/Users/{id} |
One person | 200 |
PUT /scim/v2/Users/{id} |
Replace the attributes SpeculaPro keeps | 200 |
PATCH /scim/v2/Users/{id} |
Change some attributes | 200 |
DELETE /scim/v2/Users/{id} |
Deactivate the account and end its sessions. Nobody is deleted | 204 |
Groups
| Endpoint | Does | Success |
|---|---|---|
GET /scim/v2/Groups |
List groups, optionally filtered | 200 |
POST /scim/v2/Groups |
Create a group, with members if given | 201 with a Location header |
GET /scim/v2/Groups/{id} |
One group | 200 |
PUT /scim/v2/Groups/{id} |
Replace the name and the whole member list | 200 |
PATCH /scim/v2/Groups/{id} |
Add or remove members, or rename | 200 |
DELETE /scim/v2/Groups/{id} |
Delete the group. Members stay and lose the roles its name gave them | 204 |
Tokens (signed-in administrators)
| Endpoint | Does | Permission | Success |
|---|---|---|---|
GET /api/v1/settings/sso/scim |
The base URL, the tokens, newest first (never a secret), and the privileged-roles setting | sso.manage or settings.view |
200 |
PUT /api/v1/settings/sso/scim/privileged-roles |
Let SCIM give and take away privileged roles, or stop it | sso.manage |
204, or 202 when sent for approval |
POST /api/v1/settings/sso/scim/tokens |
Create a token | sso.manage |
201, or 202 when sent for approval |
DELETE /api/v1/settings/sso/scim/tokens/{id} |
Revoke a token at once. Never needs approval | sso.manage |
204 |
People
Section titled “People”The User resource
Section titled “The User resource”| Attribute | Meaning |
|---|---|
id |
SpeculaPro’s identifier for the person |
userName |
The email address they sign in with. Required, unique in the workspace, stored trimmed and in lower case |
externalId |
The IdP’s own identifier, kept as sent |
displayName |
The name shown in SpeculaPro. Shortened to 120 characters; when it is missing or a single character, the email address is used |
name.formatted, name.givenName, name.familyName |
Used for the name when displayName isn’t sent. Returned as name.formatted only |
emails |
Read-only: the sign-in address, as one work, primary entry |
active |
false when the account is disabled |
groups |
Read-only: the groups the person is in |
meta |
resourceType, created, lastModified, location |
Anything else in a request (a password, phone numbers, the enterprise extension) is accepted and ignored.
POST /scim/v2/Users
Section titled “POST /scim/v2/Users”{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "externalId": "00u1abcd", "name": { "givenName": "Ama", "familyName": "Owusu" }, "active": true}The account is created Invited, with no password. It gets the single sign-on default role when
one is set and applies, unless that role is privileged and the workspace doesn’t let SCIM give
privileged roles; roles from groups follow when groups are pushed. "active": false creates
it disabled. An email already in the workspace answers 409 with scimType uniqueness.
GET /scim/v2/Users
Section titled “GET /scim/v2/Users”| Parameter | Meaning |
|---|---|
filter |
userName eq "…" or externalId eq "…". Nothing else is supported |
startIndex |
1-based. Default 1 |
count |
Default 100, at most 200. 0 returns only totalResults |
PATCH /scim/v2/Users/{id}
Section titled “PATCH /scim/v2/Users/{id}”Operations add, replace and remove (any letter case). Both common shapes are understood:
{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{ "op": "replace", "value": { "active": false } }] }{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{ "op": "Replace", "path": "active", "value": "False" }] }| Path | Notes |
|---|---|
active |
true or false, also as the strings "True" and "False" |
userName |
Must be an email address. Can’t be removed |
displayName, name.formatted |
Set the name |
name.givenName and name.familyName |
Set the name only when both are sent |
externalId |
remove, or a null value, clears it |
emails… |
Ignored: the sign-in address is userName |
| Anything else | Ignored |
A path may carry the schema URN in front.
What active does
Section titled “What active does”| Request | Result |
|---|---|
active: false, or DELETE |
The account is disabled and its sessions end in the same transaction. The API keys the person created are marked rotation due, or revoked when the workspace setting revoke_api_keys_on_deactivation is on |
active: true on an account SCIM disabled |
Re-enabled: Invited if the person never signed in, otherwise Active |
active: true on an account an administrator disabled |
409: … was disabled in SpeculaPro, not over SCIM: an administrator re-enables the account there |
Deactivating a break-glass administrator, or changing their userName |
403, scimType mutability |
| Deactivating the last active administrator | 409 |
Groups
Section titled “Groups”The Group resource
Section titled “The Group resource”| Attribute | Meaning |
|---|---|
id |
SpeculaPro’s identifier for the group |
displayName |
The name (1 to 512 characters), unique in the workspace. It is matched against the single sign-on Group → role table |
externalId |
The IdP’s identifier |
members |
Each {"value": "<user id>"}. Every value must be the id of a person in this workspace; an unknown one refuses the whole request |
A group has no powers of its own. While Set roles from IdP groups is on and the group’s name is in the Group → role table, a change to its members, its name or its existence sets each affected person’s roles from all their groups. See Roles from groups.
Roles that hold administration, approval or security permissions are the exception. Unless the
workspace allows it (see Privileged roles), a group
change neither gives nor removes such a role. The request still answers 200 or 201: the roles
left out are recorded in the audit log, not reported to the IdP.
GET /scim/v2/Groups
Section titled “GET /scim/v2/Groups”filter takes displayName eq "…" or externalId eq "…". startIndex and count work as for
people. excludedAttributes=members, on a list or a single group, leaves the member list out.
PATCH /scim/v2/Groups/{id}
Section titled “PATCH /scim/v2/Groups/{id}”| Operation | Path | Effect |
|---|---|---|
add |
members |
Adds the members in value |
replace |
members |
Replaces the whole member list |
remove |
members with a value |
Removes those members |
remove |
members with no value |
Removes every member |
remove |
members[value eq "<user id>"] |
Removes that member |
add, replace |
displayName |
Renames the group |
add, replace, remove |
externalId |
Sets or clears it |
add, replace |
none, with an object in value |
Sets displayName, externalId and members from the object |
Resending a state the group already has changes nothing and answers 200. A group holds at most
10,000 members, and one request names at most 10,000.
Tokens
Section titled “Tokens”GET /api/v1/settings/sso/scim
Section titled “GET /api/v1/settings/sso/scim”{ "base_url": "https://speculapro.yourbank.com.gh/scim/v2", "max_tokens": 10, "tokens": [ { "id": "…", "name": "Okta provisioning", "prefix": "scim_…", "created_at": "…", "last_used_at": "…", "expires_at": null, "revoked_at": null } ], "allow_privileged_roles": false, "role_mapping_enabled": true, "privileged_mappings": [ { "group": "SpeculaPro Admins", "role": "admin", "role_name": "Administrator" } ]}| Field | Meaning |
|---|---|
allow_privileged_roles |
Whether SCIM may give and take away privileged roles. false unless someone switched it on |
role_mapping_enabled |
Whether Set roles from IdP groups is on. Off, groups change nobody’s roles |
privileged_mappings |
The rows of the Group → role table that lead to a privileged role: group, role (the role’s key) and role_name. A privileged single sign-on default role appears with "default": true and no group. Empty when nothing maps to a privileged role |
PUT /api/v1/settings/sso/scim/privileged-roles
Section titled “PUT /api/v1/settings/sso/scim/privileged-roles”{ "allow_privileged_roles": true }A role is privileged when it holds any administration permission, any approval permission, or a security permission (managing people, single sign-on, API keys, webhooks or workspace settings). That is worked out from the role’s permissions, so it covers custom roles.
false (the default) |
SCIM never gives a privileged role and never takes one away, whatever a pushed group maps to. A person’s other mapped roles still apply |
true |
SCIM gives and removes privileged roles like any other |
| Switching on | A single sign-on change: where the workspace requires approval for those, send X-Change-Reason and the answer is 202. The request goes stale if the privileged mappings change before it is approved |
| Switching off | Applies at once, never through approval. Answers 204 |
Changing the setting changes nobody’s roles by itself; it applies to the next change the IdP
pushes. Sending the value it already has answers 204 and records nothing. Otherwise the change is
audited as scim.privileged_roles_update.
POST /api/v1/settings/sso/scim/tokens
Section titled “POST /api/v1/settings/sso/scim/tokens”Body: name (1 to 120 characters) and, optionally, expires_at (in the future). Answers 201
with token and secret. The secret is returned once; only its SHA-256 is stored.
A workspace holds at most 10 working tokens. Creating one is a single sign-on change: where the
workspace requires approval for those, send X-Change-Reason and the
answer is 202 with the request. Once approved, the person who asked collects the secret once. See
the Change requests API.
DELETE /api/v1/settings/sso/scim/tokens/{id}
Section titled “DELETE /api/v1/settings/sso/scim/tokens/{id}”Answers 204. The next request with that token is refused.
Errors
Section titled “Errors”SCIM endpoints answer in SCIM’s error format:
{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], "status": "409", "scimType": "uniqueness",| Status | scimType |
Meaning |
|---|---|---|
400 |
invalidSyntax, invalidValue, invalidFilter, invalidPath, noTarget, mutability, tooMany |
The request can’t be understood or names something that can’t be changed |
401 |
The token is missing, wrong, expired or revoked, or its workspace isn’t served at this address. Every such refusal is the same, with WWW-Authenticate: Bearer realm="SCIM" |
|
403 |
mutability |
A break-glass administrator can’t be deactivated or have their sign-in address changed |
404 |
No such person or group in this workspace | |
405 |
The method isn’t supported on that path | |
409 |
uniqueness, or none |
A duplicate userName or group name; reactivating an account an administrator disabled; deactivating the last administrator |
429 |
Too many requests. Retry-After: 30 |
|
503 |
On a multi-region service, the workspace is read-only while it is moved to another region. Retry-After: 120. Reads still work |
|
500 |
Something failed inside SpeculaPro. The detail carries a reference, never the cause |
The token endpoints use the ordinary API error format: 403 without the permission, 404 for an
unknown or already revoked token, 422 for a bad name or expiry or when ten tokens already work.