Strata PlatformDocs

SCIM provisioning

Automate user create, update, and deactivate from your identity provider with SCIM 2.0.

SCIM is set up in Organization → Provisioning. Viewing and revoking tokens there requires canManageOrgSettings; creating a token also requires canManageApiKeys, because a provisioning token can create and deactivate every account in the organization. The SCIM endpoints themselves authenticate with the per-organization bearer token, never a user session.

What SCIM does

SCIM 2.0 (System for Cross-domain Identity Management — RFC 7643 + 7644) lets your identity provider — Microsoft Entra ID, Okta, or any IdP with a SCIM connector — push user lifecycle events directly to Strata. When HR offboards an employee in your IdP, the IdP's SCIM connector sends a PATCH:

{ "Operations": [{ "op": "Replace", "path": "active", "value": false }] }

Within seconds — without any human intervention on the Strata side — the user is deactivated and every active session is revoked. This closes the "I forgot to delete Bob in Strata" compliance gap that bites every multi-app enterprise.

What is implemented today

Pilot integrations should be limited to Microsoft Entra ID first — its SCIM connector is the most forgiving and matches Strata's spec coverage. Okta is supported but uses a richer filter language than Strata's parser handles; basic create / read / deactivate works fine.

Endpoints

ResourceURLAuthWhat it does
ServiceProviderConfigGET /scim/v2/ServiceProviderConfigNoneIdP capability discovery.
ResourceTypesGET /scim/v2/ResourceTypesNoneLists the User and Group resources.
SchemasGET /scim/v2/Schemas, GET /scim/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:UserNoneThe User schema Strata implements.
List UsersGET /scim/v2/UsersBearerSupports userName eq "<email>" and externalId eq "<id>" filters, plus startIndex and count (max 200).
Get UserGET /scim/v2/Users/<id>BearerOne user. Returns 404 for a user outside your org — Strata never distinguishes "not found" from "exists in another org," so a token can't probe whether a user ID belongs to any other tenant.
Create UserPOST /scim/v2/UsersBearerPre-creates the user row. Auto-assigns the built-in user role. Returns 201. SSO sign-in remains the entry path; SCIM creation just pre-authorizes.
PATCH UserPATCH /scim/v2/Users/<id>BearerPartial update on active, displayName, externalId, userName/emails. Deactivation also revokes all sessions.
PUT UserPUT /scim/v2/Users/<id>BearerFull replace. Implemented as a sequence of PATCH replace operations.
GroupsGET, POST /scim/v2/Groups and GET, PATCH, PUT, DELETE /scim/v2/Groups/<id>BearerThe Group resource. See the note below before enabling group sync in your IdP.
Delete UserDELETE /scim/v2/Users/<id>BearerHard remove. Most IdPs prefer active: false — it preserves the audit trail.

Supported User attributes

  • userName (required, must be an email)
  • externalId (your IdP's stable identifier)
  • active (boolean)
  • displayName
  • name.givenName, name.familyName, name.formatted
  • emails[].value, emails[].type, emails[].primary

Other attributes in incoming requests are silently ignored per RFC 7644 §3.5.2.1.

What is deferred

  • Complex filters — and/or/not, pr (present), attribute paths with dots are not parsed. Only <attr> eq "<value>" works; unsupported filters fall back to "return all" so an IdP does not error out.
  • ETag concurrency and Bulk operations — not implemented.

SCIM base URL

https://app.strata.kronisys.com/scim/v2

Both the tenant URL in Entra and the connector base URL in Okta should point here. Use the canonical organization domain for your deployment.

Setting it up in Strata

Open Organization and choose Provisioning in the rail (direct URL /admin/provisioning). The section appears for admins with canManageOrgSettings and has three parts.

Provider status says whether provisioning is working, in one sentence:

  • Last synced … — when your provider last called Strata. After more than 7 whole days it reads, in amber, Last synced N days ago.
  • No provider has connected yet — a token exists but no identity provider has used it.
  • Last synced …, with a revoked token (amber) — the last call came in on a token that has since been revoked, and no live token has been used since, so your provider's next call fails unless it is given a live token.
  • Provisioning has stopped: every token has expired — every token that hasn't been revoked is past its expiry date.
  • Provisioning has stopped: every token has been revoked — your organization has had tokens, but none is left unrevoked, so your provider can't create or deactivate anyone. Both Provisioning has stopped sentences read in red.
  • Not set up yet — no token has ever been created.

Under the sentence are the directory's figures: From your directory (how many of your active members came from your identity provider), Deprovisioned, Awaiting first sign-in and Groups managed. When your provider has made changes, Recent activity folds under them with its last 25 changes. The status needs canManageUsers to read; without it the card says Needs the "Manage users" permission to read. Ask an owner. While the organization is suspended, the card also notes that your identity provider can still create and deactivate accounts.

Connect your provider holds the two values you paste into your identity provider: 1 · Tenant URL, the base URL above for your deployment, in a read-only field with a copy button, and 2 · Create a token, which gives you the secret.

Tokens lists every token that hasn't been revoked, newest first. Each row shows its name and one status — Active, Expiring in N days (within 14 days of its expiry date), Not used yet, or Expired with its date — followed by who created it and when, when it was last used, and when it expires (or No expiry), with a Revoke button. An expired token stays in the list, so the reason provisioning stopped is visible, until you revoke it or 30 days pass after it expired. Revoked tokens are one link away, under Show N revoked tokens, with who revoked them, for 30 days after they stopped working (the revocation, or the expiry if the token had already expired). A token that stopped working more than 30 days ago is no longer listed; a line under the list counts them — for example 3 older revoked tokens are in the Audit log. — with a link to the Audit log for admins who can open it, while the log still keeps their entries. Nothing is deleted: the API below still returns every token.

Creating a token

  1. Under 2 · Create a token, fill in Token name (for example Okta production). The name is optional and capped at 120 characters. Two tokens that haven't been revoked can't share a name (in any capitalization): the step says Couldn't create token: name already in use., or, when the name belongs to an expired token the list no longer shows, Couldn't create token: an expired token that is no longer listed has this name. Left blank, the token is named SCIM token 1, SCIM token 2 and so on.
  2. Choose how long it lasts under Expires after: 90 days, 180 days, 1 year (the default), 2 years, or No expiry.
  3. Click Create token (or press Enter in the name field).
  4. The Provisioning token created window shows the token, prefixed with scim_ (32 bytes of entropy, base64url-encoded), and the tenant URL. Copy it with Copy, tick I've copied the token and stored it securely, then click Done. The new token appears at the top of the list.
  5. Paste the token into your IdP's SCIM connector.

The token is shown only in that window. Strata stores only a SHA-256 hash, so a lost token cannot be recovered: revoke it and create a new one. Without canManageApiKeys the step reads Creating a provisioning token needs the "Manage API keys" permission. Ask an owner to grant it, or to create the token for you. Revoking still works.

Issuing a token is audited as scim.token_issued with the token label, its ID and its expiry date.

When provisioning is set up, the Users page also tells admins that the identity provider manages the roster, so a person added by hand is not written back to the IdP and a later sync may deactivate them.

Through the admin API

Tokens can also be managed with GET, POST and DELETE /api/admin/scim/tokens, under the same permissions. Call POST /api/admin/scim/tokens with an optional label and an optional expires_in_days, a whole number from 1 to 730. Leave expires_in_days out and the token never expires (unlike the console, whose default is one year). The response returns the token once, with a configuration cheatsheet:

{
  "ok": true,
  "token": "scim_…",
  "token_id": "…",
  "label": "Entra production sync",
  "expires_at": "2027-09-17T14:02:11.000Z",
  "created_at": "2026-09-17T14:02:11.000Z",
  "created_by_name": "Avery Quinn",
  "tenant_url": "https://app.strata.kronisys.com/scim/v2",
  "configure": {
    "tenant_url": "https://app.strata.kronisys.com/scim/v2",
    "secret_token": "scim_…",
    "notice": "This token is shown ONCE. Store it in your IdP's SCIM connector now. It stops working on 2027-09-17 — issue a replacement before then or provisioning will stop. If lost, issue a new token and revoke this one."
  }
}

A token created without an expiry returns "expires_at": null, and its notice says It does not expire. An expires_in_days outside 1 to 730 returns 400 with expires_in_days must be a whole number of days between 1 and 730. A label already used by a token that hasn't been revoked (compared without regard to capitalization) returns 409 with {"error": "Name already in use.", "code": "name_in_use", "field": "label"}; a blank label is named SCIM token N, the lowest number no token of your organization has used.

GET /api/admin/scim/tokens returns every token, revoked ones included, newest first, as { "tokens": [...], "tenant_url": "…" }. Each token carries created_by_name (the creator's name as Strata shows it, or null with created_by_former: true when the creator is no longer a member of your organization) and, for a revoked token, revoked_by_name. No email address is returned. DELETE answers { "ok": true, "revoked": true|false, "exists": true|false, "revoked_at": "…" }: revoked: false with exists: true means someone had already revoked it (at revoked_at), and exists: false means your organization has no such token.

Configuring Microsoft Entra ID (recommended)

  1. In Entra ID, go to Enterprise applications → New application → Create your own application. Name it Strata. Select Integrate any other application you don't find in the gallery (Non-gallery).
  2. After creation, go to Provisioning → Get started → Provisioning Mode: Automatic.
  3. Fill in:
    • Tenant URL: https://app.strata.kronisys.com/scim/v2
    • Secret Token: paste the scim_… token you created in Strata.
  4. Click Test Connection. Entra should report success — this calls GET /scim/v2/Users with the bearer.
  5. Save. Configure Mappings to map your Entra attributes to the SCIM User attributes Strata supports (defaults are sensible — userName → mail, active → accountEnabled, etc.).
  6. Assign users or groups under Provisioning → Users and groups and turn Provisioning Status to On.
Note

Assigning a group here is the normal way to scope provisioning, and Entra will provision that group's members as Strata users regardless of whether you also sync the group object itself. Strata now implements the SCIM /Groups resource, but group sync has not been validated end to end against Entra — see Push Groups below before switching it on.

Entra will run an initial cycle, then sync changes every ~40 minutes. Forced cycles run in 10–15 seconds.

Configuring Okta

Okta's SCIM connector lives at Applications → your app → Provisioning → Integration. The configuration is similar but the filter for User import uses syntax Strata does not parse (pr, complex AND). Stick to:

  • Create Users — works.
  • Update User Attributes — works.
  • Deactivate Users — works (sends active: false).
  • Push Groups — the PATCH operations Entra and Okta actually send are handled correctly, verified by execution against the router: adding members, removing a member by filter, renaming a group, and an authoritative replace where members you did not list are removed rather than left behind. Memberships an IdP creates are recorded as IdP-sourced, not manual.

A full tenant sync is still unproven — no real IdP has completed one end to end, so the provisioning sequence (probe-then-create ordering, retry behaviour) has not been exercised. Treat group sync as ready to trial against your own tenant, not yet as a supported configuration.

Warning

An IdP may only modify groups it created. A group you create by hand in Strata is administered in Strata: any IdP write against it is refused with 403 and scimType: "mutability", before anything is changed. So if you create Engineering in Strata and then point Entra at a group of the same name, Entra will report failures with no obvious cause. Let the IdP create the groups it intends to manage, and keep hand-made groups out of its scope.

SCIM and group-level RBAC

Strata lets an admin bind roles to a group so its members inherit those permissions (Binding roles to a group). SCIM only ever moves group membership — it never binds a role, and a role bound in Strata is invisible to your IdP. But the two features interact in one important way.

Once a group carries a role that grants elevated permissions, that group is privileged, and changing its membership becomes a privilege grant. Strata then refuses to change that membership over SCIM: an IdP administrator must never be able to mint a Strata admin by dropping a user into a group an admin happened to bind an admin role to. A SCIM add/replace members operation against a privileged group fails rather than silently granting access.

Alert

Do not bind roles to a group your IdP provisions. Membership of a privileged group must be managed by a Strata admin, by hand. Keep the roles that carry access on groups you administer inside Strata, and leave IdP-synced groups as plain labels.

The last-admin guard reaches these paths too: if a group grants admin, Strata refuses a SCIM remove that would take the org's last effective admin out of that group, counting group-conferred admins alongside direct-role admins.

Deleting a group your IdP manages from Organization → Groups removes the roles bound to it as well. The group your IdP recreates on its next sync is a new group with no roles, so when roles were bound the confirmation says Your identity provider will recreate this group on its next sync, without its roles: bind them again after it returns.

SCIM changes don't warn about agents. In the admin console, removing someone from a group or deleting a group names the agents it newly leaves without their model (<N> agents will pause). The same change made over SCIM goes through without that warning. If a group's bound role was the only thing allowing a member's agent its model, that agent pauses the next time its schedule, a webhook or another agent starts it, and its owner's inbox says why (<name> stopped).

What happens when a user is deactivated

When the IdP fires {"op":"Replace","path":"active","value":false}, Strata routes the change through the same internal path the admin console uses — so the SCIM path enforces every invariant the UI does:

  1. The org's last remaining admin can never be deactivated. If the request targets the final admin, Strata refuses it with 409 Conflict (scimType: mutability) — promote another user to admin first. An IdP misconfiguration or over-eager offboarding rule can't lock the org out.
  2. The users.is_active column flips to 0.
  3. Every session token belonging to that user is revoked. The next request from the user is rejected.
  4. An audit entry is written with action = user.disabled and actor_email = scim:<token-label> (re-activation writes action = user.enabled). The all-zeros system UUID is recorded as the actor, since no human is behind a SCIM token.

Re-activation (active: true) reverses the is_active flip but does not restore the revoked sessions — the user re-authenticates via Microsoft SSO on their next visit.

Warning

String booleans are coerced. Some Entra connector versions emit "false"/"False"/"0" for active. Strata treats these as false, so an offboarded employee is never silently left active by a string-typed value.

Email changes via SCIM

PATCH-ing userName or emails changes the email on the users row. The SSO match key is the user's email, so changing it via SCIM could detach the user from their existing Microsoft account if their Microsoft email also changed. Strata logs a [SCIM] Email change applied to <user>: <email> (verify SSO match key) warning so an operator can spot-check.

The recommended pattern is: change the email in Microsoft first, then let SCIM re-sync the new value to Strata.

Token security

  • Tokens are per-organization. A token issued for Org A can never touch Org B's users — the bearer middleware verifies the org binding on every request and the SCIM handlers scope every query to that org.
  • Tokens are stored as sha256 hashes only. The plaintext is shown once at issuance.
  • A token expires only if it was given a lifetime when it was created. Once its expiry date passes, every request with it is refused with 401 Token expired, exactly like a revoked token, and provisioning stops until you create a new token and update your connector. Create the replacement while the old token still works (its row reads Expiring in N days for the last 14 days). A token with No expiry keeps working until revoked, so rotate it proactively at least annually, or whenever the IdP that holds it is reconfigured.
  • The Authorization header is Authorization: Bearer scim_<base64url>.
  • The SCIM endpoints are rate-limited (per token, with an IP fallback) and apply a short lockout after repeated authentication failures, returning 429 when the limit is hit.

Revoking a token

Click Revoke on the token's row and confirm with Revoke token, or call DELETE /api/admin/scim/tokens/<token-id> (canManageOrgSettings required). Revoking can't be undone. The token's revoked_at column is set, with who revoked it; the next request from that token returns 401 Token invalid or revoked. The revocation is audited as scim.token_revoked with the token's name. In the console the row stays where it was, reading Revoked just now · by you, and moves under Show N revoked tokens the next time the page loads.

You can have multiple tokens active at once — for example, one for production and one for a staging IdP — and revoke them independently.

What can go wrong

FailureWhat it means
401 invalidCredentials Bearer token required.The request is missing the Authorization: Bearer … header.
401 invalidCredentials Empty bearer token.The bearer header is present but the token is empty.
401 invalidCredentials Token invalid or revoked.The token does not hash to any row, or its revoked_at is set.
401 invalidCredentials Token expired. Issue a new SCIM token in Strata and update your connector.The token's expiry date has passed. Create a new token under Provisioning and paste it into your connector.
404 User not foundThe user UUID does not exist in your org (including a user that exists in another org).
409 uniqueness User <email> already existsPOST tried to create a user whose email is already in the org. Use PATCH instead.
409 mutability Refusing to deactivate the last remaining administrator…PATCH/PUT/DELETE would remove the org's only admin. Promote another user to admin first.
400 invalidValue Operations array requiredPATCH body has no Operations array.
400 invalidValue userName must be an emailPOST body's userName does not contain @.
429 Too many failed authentication attempts / Rate limit exceeded.The token (or IP) tripped the auth-failure lockout or the request rate limit. Back off and retry.
IdP filter that worked elsewhere returns all usersFilter parser only handles <attr> eq "<value>". Anything more complex falls back to "return all".

See Users for how SCIM-created users look in the admin console, and Audit log for the scim.token_issued, scim.token_revoked, and user.disabled (scim: actor) trail.

Related