SCIM provisioning
Overview
SCIM lets your identity provider — Okta, Microsoft Entra ID, OneLogin, JumpCloud or any other that speaks SCIM 2.0 — tell Gitmoap who works at your organisation and which groups they are in, the moment it changes. You give the provider a token; from then on it creates people, puts them in groups and, when someone leaves, de-provisions them. De-provisioning takes their access to your organisation away within the same request, rather than at their next sign-in or whenever somebody remembers.
It builds on single sign-on. People still sign in through your provider; SCIM decides what is waiting for them when they do, and what is gone when they should no longer have it.
Quick start
Connect your identity provider's SCIM app
Mint a token:
gitmoap scim create-scim-token --group-id acme --name Okta. Copysecret— it is shown once.In your identity provider's SCIM app, set the base URL to your Gitmoap API address followed by
/scim/v2/organizations/<organisation-id>, and the bearer token to the secret.Turn on create users, update user attributes, deactivate users and push groups, then assign the app to a test person. They appear under Users, invited to the organisation.
Give a pushed group access:
gitmoap access create-group-access-grants --group-id acme --subjects '[{"team":"<team-id>"}]' --role '{"builtin":"developer"}'.
Before you start
- Owner of the organisation.SCIM belongs to the organisation as a whole, so its tokens are minted and revoked at the top-level group, and only an Owner can do either.
- A verified domain.Your provider can only push people whose email is on a domain your organisation has verified — the same rule every single sign-on link follows — and a change to someone's
userNameis held to the same rule. Anyone else is refused, so a misconfigured provider cannot invite the world. See single sign-on to claim and verify one. - Single sign-on, with the email your provider pushes as
userName.Gitmoap matches the person your provider pushed to the account that signs in by email address, so the two have to agree. - A token with the right scope, if you are minting SCIM tokens from the CLI or the API:
sso:readto list them andsso:manageto mint and revoke them. The SCIM token itself is used only by your provider, and only on the SCIM address; it opens nothing else.
How it works
A pushed person is expected, not a member. Most people arrive before they have ever signed in, so Gitmoap records them and invites them with your connection's default access level — or, if you already invited that email yourself, keeps your invite and the level it names. At their first sign-in the invite is taken up and they join every group your provider put them in. A push either records the person and their invite together or does neither, so a refused push leaves nothing behind for your provider's retry to trip over. Someone who already has a Gitmoap account with that email is matched to it straight away: their places on your provider's groups take effect as soon as the groups are pushed, and the invite is taken up at their next sign-in.
A pushed group is a team. Each group becomes a team whose roster your provider keeps; its name, from 1 to 100 characters, is the group's display name. You give the team access exactly as you would any other team, and everyone your provider puts in the group has it. Nobody can add or remove people on such a team by hand, or link it to a group in the sign-in instead — change the group in your provider.
De-provisioning is immediate and stays inside your organisation. When your provider deactivates or deletes someone, in one step Gitmoap removes all of their grants in your organisation — including access you gave them by hand — takes them off every one of your teams, revokes their personal access tokens that are limited to your groups and repositories, and cancels any invite still waiting for them. If they sign in again, they join nothing — even the first time they sign in through your provider with an account they already had. Their account, their access to other organisations, and everything they wrote — commits, merge requests, comments — are untouched. An approval they gave stops counting at once, because approvals are counted when a merge is checked, not when they were given.
Your provider's word on groups survives a deactivation. Gitmoap remembers which groups a de-provisioned person was in. If your provider reactivates them, they get the default access level on the organisation back and their places on your provider's groups return with it. Access they held anywhere else in the organisation does not come back on its own.
The last owner cannot be de-provisioned. Your organisation always keeps at least one person who is an Owner of it directly, with no end date. A deactivation that would remove the last of them is refused with the protocol's mutability error, and the request changes nothing — not their access, and not any other attribute it carried. Make someone else an Owner first.
SCIM replaces sign-in group sync. While your organisation has a live SCIM token, teams linked to an identity-provider group are not updated at sign-in — SCIM is the one source of truth for rosters, and two sources would undo each other. Nothing confirms the places on those teams any more, so they lapse at the end of the connection's sync period. Push those groups over SCIM instead, or revoke the last token to go back to sign-in sync.
Every change is recorded. Each SCIM request that changes something lands in your organisation's audit log, with your provider's token named as the actor; a de-provisioning, and the deletion of a pushed group, also record every grant they removed, with the role it carried. A request refused because its token is wrong is recorded without naming any token. Creating or deleting a pushed group, and the grants a deleted group took with it, are announced to webhooks subscribed to access, like any team's.
How to
Mint a token for your identity provider
Name it after the provider it is for. The response carries secret — the whole bearer token, gsc_… — once; nothing can show it again.
The web app has no screen for this yet; use the CLI or the API.
gitmoap scim create-scim-token --group-id <id> --name <name>curl -X POST https://api.gitmoap.example/groups/{group_id}/scim-tokens \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "<string>" }'See your organisation's tokens
Every token, newest first, with when it was last used. Revoked tokens stay on the list, marked with when they were revoked, so the record of who could provision is complete.
The web app has no screen for this yet; use the CLI or the API.
gitmoap scim list-scim-tokens --group-id <id>curl -X GET https://api.gitmoap.example/groups/{group_id}/scim-tokens \ -H "Authorization: Bearer $GITMOAP_TOKEN"Rotate a token
Two tokens can be live at once, so a rotation has no gap: mint a new one, put it in your provider, confirm your provider's next push succeeds, then revoke the old one.
Warning
Revoking a token takes effect on your provider's next request: every push it makes with that token is refused with 401. Revoking your organisation's last live token stops provisioning altogether, and turns sign-in group sync back on for linked teams.
The web app has no screen for this yet; use the CLI or the API.
gitmoap scim revoke-scim-token --group-id <id> --scim-token-id <id>curl -X DELETE https://api.gitmoap.example/groups/{group_id}/scim-tokens/{scim_token_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Point your identity provider at Gitmoap
Your provider does the calling; you configure it once.
| Setting in your provider | Value |
|---|---|
| SCIM base URL, or tenant URL | your Gitmoap API address, then /scim/v2/organizations/<organisation-id> |
| Authentication | HTTP header, bearer token — the secret you minted |
| Unique identifier for users | userName, set to the person's email |
| Supported actions | create users, update user attributes, deactivate users, push groups |
Your provider can read what Gitmoap supports from ServiceProviderConfig, ResourceTypes and Schemas under the same address. Filtering supports userName eq for people and displayName eq for groups — the lookups every provider makes before it creates something.
Remove somebody's access
Unassign them from the app in your provider, or deactivate them there. Your provider sends the deactivation, and their access to your organisation is gone when it returns. To give it back, reassign them.
Warning
De-provisioning removes every grant the person holds in your organisation, including the ones you gave them by hand, and revokes their personal access tokens limited to it. Reactivating them restores the default access level and their places on your provider's groups — nothing else. Deleting a group in your provider deletes its team, and every grant the team held goes with it.
Reference
18 endpoints
POST/groups/{group_id}/scim-tokenssso:manage
Mint a token for the organization's identity provider to provision with.
Token scope
sso:manage
Counts against
scim_tokens_per_org
Send
Path
- group_iduuidThe organization's top-level group
Body
- namestringWhat it is for, such as the identity provider it is given to. 1–100 characters.
Refuses
400Invalid request, or the group is not an organization's top-level group401Authentication required403Only the organization's owners mint SCIM tokens404Group not found409The request conflicts with the resource's current state; `reason` says how429The organization already holds as many live tokens as it may
Receive
No body.
GET/groups/{group_id}/scim-tokenssso:read
The organization's SCIM tokens, live and revoked.
Token scope
sso:read
Send
Path
- group_iduuidThe organization's top-level group
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Authentication required403Only the organization's owners see its SCIM tokens404Group not found
Receive
tokens[]object7 fields
- created_atdate-time
- created_byobjectoptional
- last_used_atdate-timeoptional
- namestring
- organization_iduuid
- revoked_atdate-timeoptionalWhen it was revoked. A revoked token is kept for the audit trail and authenticates nothing.
- scim_token_iduuid
DELETE/groups/{group_id}/scim-tokens/{scim_token_id}sso:manage
Revoke a SCIM token.
Token scope
sso:manage
Send
Path
- group_iduuidThe organization's top-level group
- scim_token_iduuidSCIM token ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Authentication required403Only the organization's owners revoke SCIM tokens404Group or live token not found409The request conflicts with the resource's current state; `reason` says how
Receive
No body.
GET/scim/v2/organizations/{group_id}/ServiceProviderConfigpublic
What this SCIM endpoint supports.
get_scim_service_provider_configGET /scim/v2/organizations/{group_id}/ServiceProviderConfigOpen in the API referenceToken scope
None — this one is public
Send
Path
- group_iduuidThe organization's top-level group
Refuses
401No valid SCIM token of this organization
Receive
No body.
GET/scim/v2/organizations/{group_id}/ResourceTypespublic
The resource types this SCIM endpoint serves.
Token scope
None — this one is public
Send
Path
- group_iduuidThe organization's top-level group
Refuses
401No valid SCIM token of this organization
Receive
No body.
GET/scim/v2/organizations/{group_id}/Schemaspublic
The attributes this SCIM endpoint reads and writes.
Token scope
None — this one is public
Send
Path
- group_iduuidThe organization's top-level group
Refuses
401No valid SCIM token of this organization
Receive
No body.
GET/scim/v2/organizations/{group_id}/Userspublic
The people the identity provider has pushed, or the one a `userName eq` filter names.
Token scope
None — this one is public
Send
Path
- group_iduuidThe organization's top-level group
Query
- filterstringoptional
userName eq "…" - startIndexint64optional1-based index of the first result
- countint64optionalResults per page, at most 100
Refuses
400An unsupported filter401No valid SCIM token of this organization
Receive
Resources[]object7 fields
- activebooleanoptional
falsetakes away the person's access to the organization;truegives it back. - displayNameobjectoptional
- externalIdobjectoptional
- idobjectoptional
- metaobjectoptional
- nameobjectoptional
- userNamestringThe person's sign-in name — for gitmoap, their email address.
- activebooleanoptional
- itemsPerPageint64
- startIndexint64
- totalResultsint64
GET/scim/v2/organizations/{group_id}/Users/{scim_user_id}public
One person the identity provider pushed.
Token scope
None — this one is public
Send
Path
- group_iduuidThe organization's top-level group
- scim_user_iduuidSCIM user ID
Refuses
401No valid SCIM token of this organization404User not found
Receive
- activebooleanoptional
falsetakes away the person's access to the organization;truegives it back. - displayNameobjectoptional
emails[]object3 fields
- primaryobjectoptional
- typeobjectoptional
- valuestring
- externalIdobjectoptional
groups[]object2 fields
- displayobjectoptional
- valuestringThe id of the user or group.
- idobjectoptional
- metaobjectoptional
- nameobjectoptional
- userNamestringThe person's sign-in name — for gitmoap, their email address.
POST/scim/v2/organizations/{group_id}/Userspublic
Provision a person.
Token scope
None — this one is public
Counts against
members_per_scope
Send
Path
- group_iduuidThe organization's top-level group
Body
- activebooleanoptional
falsetakes away the person's access to the organization;truegives it back. - displayNameobjectoptional
emails[]object3 fields
- primaryobjectoptional
- typeobjectoptional
- valuestring
- externalIdobjectoptional
groups[]object2 fields
- displayobjectoptional
- valuestringThe id of the user or group.
- idobjectoptional
- metaobjectoptional
- nameobjectoptional
- userNamestringThe person's sign-in name — for gitmoap, their email address.
Refuses
400An invalid user, or an email outside the organization's verified domains401No valid SCIM token of this organization409A user with that userName already exists429The organization holds as many people as it may
Receive
The same shape get_scim_user returns — 9 fields. Go to it
Show it here
- activebooleanoptional
falsetakes away the person's access to the organization;truegives it back. - displayNameobjectoptional
emails[]object3 fields
- primaryobjectoptional
- typeobjectoptional
- valuestring
- externalIdobjectoptional
groups[]object2 fields
- displayobjectoptional
- valuestringThe id of the user or group.
- idobjectoptional
- metaobjectoptional
- nameobjectoptional
- userNamestringThe person's sign-in name — for gitmoap, their email address.
PUT/scim/v2/organizations/{group_id}/Users/{scim_user_id}public
Replace a person's attributes; `active: false` de-provisions them.
replace_scim_userPUT /scim/v2/organizations/{group_id}/Users/{scim_user_id}Open in the API referenceToken scope
None — this one is public
Send
Path
- group_iduuidThe organization's top-level group
- scim_user_iduuidSCIM user ID
Body
- activebooleanoptional
falsetakes away the person's access to the organization;truegives it back. - displayNameobjectoptional
emails[]object3 fields
- primaryobjectoptional
- typeobjectoptional
- valuestring
- externalIdobjectoptional
groups[]object2 fields
- displayobjectoptional
- valuestringThe id of the user or group.
- idobjectoptional
- metaobjectoptional
- nameobjectoptional
- userNamestringThe person's sign-in name — for gitmoap, their email address.
Refuses
400An invalid user401No valid SCIM token of this organization404User not found409The person is the organization's last owner, or the userName is taken
Receive
The same shape get_scim_user returns — 9 fields. Go to it
Show it here
- activebooleanoptional
falsetakes away the person's access to the organization;truegives it back. - displayNameobjectoptional
emails[]object3 fields
- primaryobjectoptional
- typeobjectoptional
- valuestring
- externalIdobjectoptional
groups[]object2 fields
- displayobjectoptional
- valuestringThe id of the user or group.
- idobjectoptional
- metaobjectoptional
- nameobjectoptional
- userNamestringThe person's sign-in name — for gitmoap, their email address.
PATCH/scim/v2/organizations/{group_id}/Users/{scim_user_id}public
Change some of a person's attributes; `active: false` de-provisions them.
patch_scim_userPATCH /scim/v2/organizations/{group_id}/Users/{scim_user_id}Open in the API referenceToken scope
None — this one is public
Send
Path
- group_iduuidThe organization's top-level group
- scim_user_iduuidSCIM user ID
Body
Operations[]object3 fields
- opstring
add,removeorreplace, in any case. - pathobjectoptional
- valueobjectoptional
- opstring
Refuses
400An unsupported operation, or more than 100401No valid SCIM token of this organization404User not found409The person is the organization's last owner
Receive
The same shape get_scim_user returns — 9 fields. Go to it
Show it here
- activebooleanoptional
falsetakes away the person's access to the organization;truegives it back. - displayNameobjectoptional
emails[]object3 fields
- primaryobjectoptional
- typeobjectoptional
- valuestring
- externalIdobjectoptional
groups[]object2 fields
- displayobjectoptional
- valuestringThe id of the user or group.
- idobjectoptional
- metaobjectoptional
- nameobjectoptional
- userNamestringThe person's sign-in name — for gitmoap, their email address.
DELETE/scim/v2/organizations/{group_id}/Users/{scim_user_id}public
De-provision a person and forget them.
delete_scim_userDELETE /scim/v2/organizations/{group_id}/Users/{scim_user_id}Open in the API referenceToken scope
None — this one is public
Send
Path
- group_iduuidThe organization's top-level group
- scim_user_iduuidSCIM user ID
Refuses
401No valid SCIM token of this organization404User not found409The person is the organization's last owner
Receive
No body.
GET/scim/v2/organizations/{group_id}/Groupspublic
The teams the identity provider keeps, or the one a `displayName eq` filter names.
Token scope
None — this one is public
Send
Path
- group_iduuidThe organization's top-level group
Query
- filterstringoptional
displayName eq "…" - startIndexint64optional1-based index of the first result
- countint64optionalResults per page, at most 100
Refuses
400An unsupported filter401No valid SCIM token of this organization
Receive
Resources[]object4 fields
- displayNamestring
- externalIdobjectoptional
- idobjectoptional
- metaobjectoptional
- itemsPerPageint64
- startIndexint64
- totalResultsint64
GET/scim/v2/organizations/{group_id}/Groups/{team_id}public
One team the identity provider keeps.
Token scope
None — this one is public
Send
Path
- group_iduuidThe organization's top-level group
- team_iduuidSCIM group ID — the team's ID
Refuses
401No valid SCIM token of this organization404Group not found
Receive
- displayNamestring
- externalIdobjectoptional
- idobjectoptional
members[]object2 fields
- displayobjectoptional
- valuestringThe id of the user or group.
- metaobjectoptional
POST/scim/v2/organizations/{group_id}/Groupspublic
Create a team the identity provider keeps.
Token scope
None — this one is public
Counts against
teams_per_org
Send
Path
- group_iduuidThe organization's top-level group
Body
- displayNamestring
- externalIdobjectoptional
- idobjectoptional
members[]object2 fields
- displayobjectoptional
- valuestringThe id of the user or group.
- metaobjectoptional
Refuses
400An invalid group401No valid SCIM token of this organization409A group with that name or externalId exists429The organization holds as many teams as it may
Receive
The same shape get_scim_group returns — 5 fields. Go to it
Show it here
- displayNamestring
- externalIdobjectoptional
- idobjectoptional
members[]object2 fields
- displayobjectoptional
- valuestringThe id of the user or group.
- metaobjectoptional
PUT/scim/v2/organizations/{group_id}/Groups/{team_id}public
Replace a group's name and members.
Token scope
None — this one is public
Send
Path
- group_iduuidThe organization's top-level group
- team_iduuidSCIM group ID — the team's ID
Body
- displayNamestring
- externalIdobjectoptional
- idobjectoptional
members[]object2 fields
- displayobjectoptional
- valuestringThe id of the user or group.
- metaobjectoptional
Refuses
400An invalid group401No valid SCIM token of this organization404Group not found
Receive
The same shape get_scim_group returns — 5 fields. Go to it
Show it here
- displayNamestring
- externalIdobjectoptional
- idobjectoptional
members[]object2 fields
- displayobjectoptional
- valuestringThe id of the user or group.
- metaobjectoptional
PATCH/scim/v2/organizations/{group_id}/Groups/{team_id}public
Add or remove members, or rename a group.
Token scope
None — this one is public
Send
Path
- group_iduuidThe organization's top-level group
- team_iduuidSCIM group ID — the team's ID
Body
Operations[]object3 fields
- opstring
add,removeorreplace, in any case. - pathobjectoptional
- valueobjectoptional
- opstring
Refuses
400An unsupported operation, or more than 100401No valid SCIM token of this organization404Group not found
Receive
The same shape get_scim_group returns — 5 fields. Go to it
Show it here
- displayNamestring
- externalIdobjectoptional
- idobjectoptional
members[]object2 fields
- displayobjectoptional
- valuestringThe id of the user or group.
- metaobjectoptional
DELETE/scim/v2/organizations/{group_id}/Groups/{team_id}public
Delete a team the identity provider keeps, with every grant it held.
Token scope
None — this one is public
Send
Path
- group_iduuidThe organization's top-level group
- team_iduuidSCIM group ID — the team's ID
Refuses
401No valid SCIM token of this organization404Group not found
Receive
No body.
Limits
Two live SCIM tokens per organisation, on every plan — enough to rotate without a gap. Minting a third is refused with 429 TOO_MANY_REQUESTS; revoke the old one first. An organisation that provisions from more than one identity provider can ask for a higher limit, up to five.
People your provider pushes count toward your organisation's member limit, and groups count toward its team limit. See teams and billing and plans for the numbers on each plan. Creating a person or a group past its limit is refused with 429, and your provider retries it once you have made room.
A group's people count toward the people-per-team limit, and a full team takes nobody new without refusing the push. Gitmoap still records that your provider put the person in the group, and gives them their place at their next sign-in if the team has room by then.
A PATCH carries at most 100 operations, and a page of results at most 100 resources. Every provider pages and batches below both.
Requests to the SCIM address are rate-limited per address, with a limit of their own, so a sync never counts against the limit every other client shares. A first full sync of a large directory sends thousands of requests in a few minutes and can still reach it. A request over it is refused with 429 and no SCIM error body; identity providers back off and retry on their own.
Troubleshooting
401on every SCIM request
- Cause
- The token is wrong, revoked, or was minted for a different organisation than the one in the address
- What to do
- Check the organisation id in the base URL, then mint a new token and paste the whole
gsc_…value
400with invalidValue, naming a domain
- Cause
- The person's email — or the new
userNamean update sends — is on a domain your organisation has not verified - What to do
- Verify the domain, or leave the person out of the app's assignments
400with invalidValue, about a group's name
- Cause
- The group's display name is empty or longer than 100 characters
- What to do
- Rename the group in your provider
400with invalidSyntax
- Cause
- The request body is not the user or group the address expects, or is not JSON
- What to do
- Check the attribute mappings in your provider; if you are calling by hand, send the resource the protocol defines
400with invalidValue, about userName
- Cause
- Your provider sent a
userNamethat is not an email address, and no email to fall back on - What to do
- Map
userNameto the person's email in your provider's attribute mappings
400with tooMany
- Cause
- A PATCH carried more than 100 operations
- What to do
- Your provider batches on its own; if you are calling by hand, split the request
409with mutability on a deactivation
- Cause
- The person is your organisation's last owner
- What to do
- Make someone else an Owner, then deactivate them again
409with uniqueness on creating a person
- Cause
- Your provider already pushed someone with that email
- What to do
- Your provider should look the person up with
userName eqfirst; re-run its import to match the existing record
429on a push
- Cause
- Your organisation's member limit or team limit
- What to do
- Make room, or raise the plan or the limit, then retry the push from your provider
429minting a token
- Cause
- Your organisation already has two live SCIM tokens
- What to do
- Revoke the one you are replacing, or ask for a higher limit
429with no SCIM error body, during a large sync
- Cause
- The SCIM address's rate limit
- What to do
- Let the burst pass; your provider retries on its own, or retry the failed pushes from it
Someone your provider put in a group is not on its team
- Cause
- The team was at its people-per-team limit when the group was pushed
- What to do
- Make room on the team, or raise the limit; they get their place at their next sign-in
Somebody pushed cannot see anything
- Cause
- They have not signed in since they were pushed — access waits for the first sign-in
- What to do
- Ask them to sign in through your provider
A pushed group's team has no access
- Cause
- A group grants nothing by itself
- What to do
- Give the team access, as for any team
People on a linked team stopped following sign-ins, then dropped off it
- Cause
- Your organisation has a live SCIM token, which turns sign-in group sync off, so nothing confirms their places and they lapse at the end of the sync period
- What to do
- Push the group over SCIM instead, or revoke the last SCIM token to use sign-in sync
Examples
What your provider sends to create a person
{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "userName": "ada@acme.com", "name": { "givenName": "Ada", "familyName": "Lovelace" }, "emails": [{ "value": "ada@acme.com", "primary": true, "type": "work" }], "externalId": "00u1abcd", "active": true}What de-provisioning looks like on the wire
{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{ "op": "replace", "path": "active", "value": false }]}Find the tokens nobody has used in a month
gitmoap scim list-scim-tokens --group-id acme --output json \ | jq -r --arg cutoff "$(date -u -v-30d +%FT%TZ)" \ '.tokens[] | select(.revoked_at == null and ((.last_used_at // "") < $cutoff)) | .scim_token_id'FAQ
Does deactivating someone delete their account?
No. It takes away their access to your organisation. Their account belongs to them, and may be a member of other organisations.
Can I still add people by hand?
Yes. Access you give by hand stays until you change it — with one exception: de-provisioning someone removes every grant they hold in your organisation, however it was given. A team your provider keeps is also your provider's: nobody changes its people by hand.
What happens to a pushed group when it is deleted in my provider?
The team is deleted, and every grant it held goes with it. Its people keep whatever they hold in their own right.
Why is the person's email the user name?
Gitmoap identifies a person by their verified email address when they sign in through your provider, so that is what a pushed person has to be matched by.
Related
- Single sign-onthe connection, the verified domains SCIM depends on, and the default access level pushed people are invited at
- Teamswhat a pushed group becomes, and how to give it access
- Accessseeing what someone can do, and why
- Audit logevery change your provider made