Identity
SCIM provisioning
Enterprise identity providers (Okta, Entra ID, OneLogin and others) create, update and deactivate users in your app over SCIM 2.0. A mock identity provider can do the same: it pushes its test users and groups to your app's SCIM endpoint and grades every answer against RFC 7643 and RFC 7644, so you find the mistakes before a customer's IdP does.
SCIM provisioning is included from the Team plan. On the Free plan, setting a target or starting a run answers 402 with "code": "feature_unavailable".
Set up a target
On the IdP's Provisioning tab (owners and admins), enter:
| Setting | Notes |
|---|---|
| SCIM base URL | For example https://staging.example.com/scim/v2. Requests go to /Users and /Groups below it. It must be reachable from the internet: private, loopback and link-local addresses are refused |
| Bearer token | At least 8 characters, sent as Authorization: Bearer <token>. It is encrypted at rest, never shown again or returned by the API, and redacted from recorded requests and responses |
Changing the base URL keeps the token unless you enter a new one, and forgets the remote IDs of the old target.
Requests use Content-Type: application/scim+json and Accept: application/scim+json, application/json, a 10-second timeout and a 1 MB limit on each response. Runs execute in the background, one at a time per IdP, and are kept for 30 days.
Actions
| Action | Requests |
|---|---|
| Push all users | For each user: POST /Users (expects 201), or PUT /Users/<id> once it was provisioned. On 409, GET /Users?filter=userName eq "…" and adopt the existing user |
| Push user | The same for one user |
| Update (PATCH) | PATCH /Users/<id> with a PatchOp that replaces name, displayName, emails and active |
| Update (PUT) | PUT /Users/<id> with the full representation |
| Deactivate | PATCH /Users/<id> with { "op": "replace", "value": { "active": false } } |
| Delete | DELETE /Users/<id> (expects 204); the remote ID is forgotten |
| Push groups | Pushes users that were not provisioned yet, then for each group POST /Groups or PUT /Groups/<id> with members (the users' remote IDs) |
| Compliance check | The sequence below |
Users are sent with the core User schema: userName is the email, externalId the test user's subject, plus name, displayName, emails (primary, work) and active. The remote IDs your app returns are kept per test user (scimId in the users API) and per group name. A run handles up to 500 users and 100 groups.
Compliance check
The compliance check runs an Okta-style sequence with a throwaway scim-check-<random>@example.com user:
- Create the user (expects
201). - Get it by ID (expects
200). - Filter by
userName eq "…"(expects200and a ListResponse with exactly that user). - Create the same user again (expects
409). PATCHit toactive: false(expects200withactive: false, or204).- Delete it (expects
204). - Get an unknown ID (expects
404).
Grading
Every step is graded. Must checks decide whether the step passes; should checks are warnings:
| Answer | Must | Should |
|---|---|---|
| A resource (create, get, replace, patch) | The expected status; Content-Type: application/scim+json; schemas has the core User or Group URN; id is a string; the same id and the echoed userName; meta.resourceType |
meta.location; meta.created and meta.lastModified; a Location header on 201 |
| A ListResponse | 200; the content type; schemas has the ListResponse URN; totalResults is a number; Resources is an array; the filter returns exactly the match |
startIndex and itemsPerPage |
An error (409, 404) |
The expected status; the content type; schemas has the Error URN; status repeats the HTTP status |
status is a string; scimType: "uniqueness" on 409 |
| A delete | 204 |
An empty body |
A transport error (a timeout, a refused connection, a response over 1 MB) fails the step. Each step shows the request and response, headers and body, with the token redacted, in the run details.
From the API
# Set the target once
curl -X PUT https://app.testlie.com/api/idps/$IDP_ID/scim \
-H "Authorization: Bearer $TESTLIE_API_KEY" -H "Content-Type: application/json" \
-d '{ "baseUrl": "https://staging.example.com/scim/v2", "token": "'"$SCIM_TOKEN"'" }'
# Run the compliance check and wait for the result
RUN=$(curl -s -X POST https://app.testlie.com/api/idps/$IDP_ID/scim/runs \
-H "Authorization: Bearer $TESTLIE_API_KEY" -H "Content-Type: application/json" \
-d '{ "action": "compliance" }' | jq -r .run.id)
until STATUS=$(curl -s -H "Authorization: Bearer $TESTLIE_API_KEY" \
https://app.testlie.com/api/idps/$IDP_ID/scim/runs/$RUN | jq -r .run.status) && [ "$STATUS" != queued ] && [ "$STATUS" != running ]; do sleep 2; done
echo "$STATUS" # passed, failed or errorThe API actions are push_all, push_user, update_patch, update_put, deactivate, delete, push_groups and compliance; the per-user ones take a userId. Starting a run while another one of the same IdP is in progress answers 409. The Identity API lists every endpoint and field.
Owners and admins configure targets and start runs; every change and run is recorded in the team's audit log. Members can read the target (without the token) and the runs.