Skip to main content

SCIM inbound provisioning

Let your HR system or directory be the source of truth, so joiners, movers and leavers happen without anybody remembering. Endpoints, filters, and what maps to what.

Written for whoever runs IT9 min readUpdated

SCIM is a standard way for one system to keep accounts in another up to date. Your HR system or directory already knows who joined on Monday, who changed department, and whose last day was Friday. SCIM lets it push those facts here, so a Qorionix account appears, changes and is switched off without anybody having to remember.

The leaver half is what matters. Most organisations create accounts reliably, because somebody is waiting for one; almost nobody removes them reliably, because nobody is waiting for that. An account that still works three months after its owner left is one of the most common findings in a real assessment, and automating that path is the single most effective thing on this page.

The current wiring status, stated plainly

This feature has a history worth knowing, because you may find a document — including one of ours — that describes it wrongly. The SCIM server used to write into a pair of shadow tables of its own. A person provisioned there had no login, no organisation membership, no role and no groups, so a push from Okta or Workday appeared to succeed and produced nothing the sign-in path or an administrator could see.

Real limits remain, and they are in the sections below rather than a footnote: the role every provisioned user receives is fixed, two discovery endpoints the standard defines do not exist, and an unparseable filter returns the wrong HTTP status. None of those stops the feature working; all of them change how you configure your provider.

The endpoints

The base path is /api/v1/scim/v2. Give your provider that as the SCIM base URL.

The SCIM 2.0 surface
GET    /api/v1/scim/v2/ServiceProviderConfig

GET    /api/v1/scim/v2/Users              POST   /api/v1/scim/v2/Users
GET    /api/v1/scim/v2/Users/{id}         PUT    /api/v1/scim/v2/Users/{id}
PATCH  /api/v1/scim/v2/Users/{id}         DELETE /api/v1/scim/v2/Users/{id}

GET    /api/v1/scim/v2/Groups             POST   /api/v1/scim/v2/Groups
GET    /api/v1/scim/v2/Groups/{id}        PUT    /api/v1/scim/v2/Groups/{id}
PATCH  /api/v1/scim/v2/Groups/{id}        DELETE /api/v1/scim/v2/Groups/{id}

What /ServiceProviderConfig advertises: patch supported, filtering supported with a maximum of 200 results, bulk not supported, password change not supported, sorting not supported, entity tags not supported, and OAuth bearer token as the only authentication scheme.

Pagination uses the standard parameters. startIndex defaults to 1, count defaults to 100, and 200 is the ceiling. A count above 200 is not an error — it is ignored and you get 100, so a provider asking for 500 a page silently receives far fewer and takes longer rather than failing.

The token, and why the URL has no organisation in it

Every other administrative path in the platform carries your organisation in the URL. These do not, deliberately: an HR system has no user session and cannot refresh one, and a revoked employee must not be able to take the payroll feed down with them by having their own account disabled. The only credential accepted here is a dedicated SCIM bearer token, which is also what tells us which organisation the request belongs to. An ordinary user session does not authenticate here at all.

Minting a token
GET    /api/v1/organizations/{orgID}/scim/tokens        sso:read
POST   /api/v1/organizations/{orgID}/scim/tokens        sso:write
DELETE /api/v1/organizations/{orgID}/scim/tokens/{id}   sso:delete

400  scim token: organization id is required
400  scim token: name is required

These are gated on the SSO permission family rather than something weaker, because a SCIM token can create users and memberships. An ungated mint let any member of an organisation provision themselves an administrator without ever leaving the tenant.

The token format
zqscim_ followed by 43 characters — 32 bytes of cryptographic randomness. The prefix exists so a leaked secret is recognisable in a log or a pasted snippet without resolving it against the database.
Shown exactly once
The full token is returned by the mint call and never again; only its hash and first 14 characters are kept. Put it into your provider immediately. If you lose it, mint a new one and revoke the old.
Stored as a hash
SHA-256, not a slow password hash. The token is 256 bits of random output, so it has no guessable structure for a slow hash to protect.
Revoked tokens are kept
Revoking marks the row dead rather than deleting it, so the token list includes dead tokens — an auditor needs to see that a credential existed and when it stopped.
Authentication failures
401  missing or invalid Bearer token
401  invalid SCIM token

A disabled, revoked, expired and never-existed token are deliberately indistinguishable — same message, same timing — because anything else turns the endpoint into a way of testing which tokens are real. The scheme is also matched case-sensitively: Bearer <token> works and bearer <token> does not, which trips up hand-written integrations more often than you would think.

What maps to what

How a SCIM user attribute lands in Qorionix. Attribute names are matched case-insensitively; the set is a fixed allow-list, not an open mapping.
SCIM attributeBecomes
userNameThe email address, which is the account identifier here
emails.valueThe same email address
name.givenNameFirst name
name.familyNameLast name
displayNameStored on the profile as displayName
externalIdKept alongside the user so your directory's own identifier survives
activeWhether the account can sign in
idThe Qorionix user identifier

For groups: displayName is the group name, externalId your directory's identifier, members the memberships. Groups created this way are ordinary Qorionix groups.

A newly created user gets the employee user type, an unverified email address, a record that SCIM provisioned it, and a password value that is deliberately not a valid hash — so the account exists but cannot be signed in to with a password. That is the right default for a federated-only identity. If these people also need a password, set one through the ordinary invitation flow; see Inviting your team.

One case is worth knowing in advance. Email addresses are unique across the whole platform, so if the person your feed pushes already exists — a contractor shared between two customers — the existing identity is adopted into your organisation rather than duplicated: they gain membership and a mapping, and that is all. Your feed does not get to rewrite that shared account's name, status or credential, because that would be one customer writing into another's data.

The supported operations

Create, read, replace, patch and delete, on users and groups. Patch supports the three standard operations — add, remove, replace — and acts on an explicit list of attributes rather than anything you send.

  • On a user: active, userName, externalId, displayName, name.givenName, name.familyName, name.formatted, and emails. userName cannot be emptied or removed.
  • On a group: displayName, externalId and members, including the filtered member paths providers use to remove one person without resending the whole list.
  • Anything outside those lists is refused with invalid SCIM patch path and the attribute named, rather than silently ignored — an ignored patch is how two systems disagree for months.

Filters

Your provider will use filters constantly — to check whether a person exists before creating them, and to reconcile its view with ours. The grammar is the standard one: eq, ne, co, sw, ew, gt, ge, lt, le, the presence test pr, and and, or, not(...) with parentheses.

The attributes you may filter on are an allow-list, and that is a security decision rather than tidiness: answering an unknown attribute with an unfiltered result would hand the whole directory to a probe like filter=nickName eq "x". Filterable on users: userName, externalId, active, emails.value, name.givenName, name.familyName, displayName, id. On groups: displayName, externalId, id.

Filter errors, verbatim
invalid SCIM filter
invalid SCIM filter: unsupported resource type "<rt>"
invalid SCIM filter: unexpected "<tok>" at end of filter

It fails loudly because the previous version failed quietly: it turned any filter it could not parse into a substring search for the filter's own text, which matched nothing for every real provider probe, so Okta's import reported 0 users with nothing in any log to explain why.

The error body shape — note that status is a string, per the standard
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
  "detail": "<what went wrong>",
  "status": "<number>"
}

Setting it up

  1. Mint a token and name it after the system that will hold it

    One token per connected system, so revoking the Workday feed does not break the Entra ID feed and the history tells you which system did what.

  2. Give your provider the base URL and the token

    Base URL /api/v1/scim/v2, authentication type OAuth bearer token, the token value exactly as returned including the zqscim_ prefix.

  3. Map only the attributes in the table above

    Anything else is either ignored on write or a hard failure on a filter. Resist mapping your whole HR schema across; the extra attributes are not validated and do not drive behaviour.

  4. Test with one person, then with one leaver

    Provision a test user and confirm they appear in your user list with a role. Then deactivate them in your directory and confirm the account goes inactive and drops out of its groups. The second test is the one that will matter.

What to do next

  1. Set up provisioning before federated sign-in. Accounts arriving and leaving correctly is worth more than the sign-in experience, and it is the half that is finished.
  2. If you also want federated sign-in, read Single sign-on with SAML — its path resolves an assertion to a user who must already exist, which is exactly what SCIM gives you.
  3. Make sure every provisioned account has a second factor. Set up two-factor authentication.
  4. If your provider will not finish its setup wizard, or reports the connection as unhealthy, read the two callouts above first — between them they explain most of it. Otherwise, get in touch.

Was this article wrong?

If a procedure here does not match what you see, or a limit we described has changed, tell us and we will fix the page. Email us about this article, or see how to get help if you need an answer rather than a correction.

Everything in identity and access