Prompt
How do I build SCIM provisioning for SaaS customers?
Latest observation
Building SCIM provisioning for SaaS customers usually means supporting SCIM 2.0 so customer identity systems can create, update, disable, and sometimes group-manage users in your app automatically.
Here’s a practical way to build it.
1) Decide what you’re provisioning
At minimum, most SaaS apps support:
- Users
- Create user
- Update user attributes
- Deactivate/reactivate user
- Read user by ID / filter by external identifier
- Groups (optional, but common)
- Create/read group
- Add/remove members
- Use group membership for roles/entitlements
Most vendors start with Users only, then add Groups later.
2) Implement the SCIM 2.0 endpoints
SCIM 2.0 typically requires these endpoints:
Service provider config
GET /scim/v2/ServiceProviderConfig
Schemas
GET /scim/v2/SchemasGET /scim/v2/Schemas/UserGET /scim/v2/Schemas/Group
Resource types
GET /scim/v2/ResourceTypesGET /scim/v2/ResourceTypes/UserGET /scim/v2/ResourceTypes/Group
Users
POST /scim/v2/UsersGET /scim/v2/UsersGET /scim/v2/Users/{id}PUT /scim/v2/Users/{id}PATCH /scim/v2/Users/{id}DELETE /scim/v2/Users/{id}
Groups
- Same pattern under
/Groups
If you want broad IdP compatibility, these are the typical endpoints customers expect.
3) Use SCIM’s identity model correctly
SCIM has some important conventions:
Required user fields
Commonly:
userName— usually the login/emailactive— whether user is provisioned/enabledname.givenName,name.familyName— optional but commonemails— often one primary emailexternalId— customer-side identifier from the IdP
Important IDs
id= your internal SCIM resource IDexternalId= the IdP’s ID for the user in their system
You should store both and map them carefully.
Normalization
Be consistent:
- Treat email/userName case-insensitively if your product does
- Decide whether
userNameis immutable - Use one canonical internal user record, then map SCIM fields to it
4) Support both creation and linking flows
Customers may want to:
- Provision a new user
- Match an existing user in your SaaS app
- Deactivate/reactivate users over time
Best practice:
- Match first by
externalId - Then by
userNameor email if needed - Prevent duplicate accounts when re-provisioning
A robust linking strategy saves a lot of support issues.
5) Handle PATCH properly
Many IdPs use PATCH heavily, especially for updates and deactivation.
Typical operations:
replaceactivereplacename.givenNamereplaceemailsadd/removegroup members
Your PATCH implementation should:
- Be idempotent
- Ignore unknown attributes if possible
- Return SCIM-compliant error responses for invalid operations
6) Represent deprovisioning as active: false
When the IdP disables a user, usually they send:
PATCH /Users/{id}active = false
Don’t delete the user record unless you explicitly want that behavior.
Typical SaaS behavior:
- Set account to disabled/suspended
- Preserve history, ownership, audit logs, and references
- Prevent login/access
- Optionally remove sessions/tokens
7) Add auth for SCIM endpoints
SCIM itself doesn’t define one auth mechanism, so you choose what your SaaS supports.
Common options:
- Bearer token generated per customer
- Basic auth with a long-lived secret
- OAuth bearer token if you support OAuth-based provisioning
Best practice:
- Use a tenant-scoped token or connection secret
- Restrict endpoints to the right tenant
- Rotate/revoke credentials safely
Also ensure every SCIM request is mapped to the correct customer tenant.
8) Make it multi-tenant-safe
This is one of the biggest design points.
Every SCIM request must be scoped to the customer that owns it.
Ways to do this:
- Separate SCIM base URL per tenant, e.g.
https://api.example.com/scim/v2/tenants/{tenantId}/Users - Or use a shared SCIM endpoint plus a tenant-specific token
- Or use tenant-specific subdomains
Store:
- Tenant ID
- SCIM auth credential
- ExternalId mapping per tenant
- SCIM resource IDs per tenant
Never allow one customer’s IdP to manage another customer’s users.
9) Support filtering and pagination
Many IdPs query SCIM resources to find existing users.
You’ll need:
GET /Users?filter=userName eq "alice@example.com"GET /Users?filter=externalId eq "abc123"- Pagination via
startIndexanditemsPerPage totalResultsin responses
Even if you don’t need full search internally, supporting basic filtering is important for interoperability.
10) Return SCIM-compliant responses and errors
SCIM responses should include the right fields:
schemasidmeta.resourceTypemeta.createdmeta.lastModifiedversionormeta.versionif supported
Errors should follow SCIM format:
statusscimTypeif applicabledetail
Examples:
400invalid value401unauthorized404not found409uniqueness conflict
11) Make attribute mapping configurable
Customers often want to map:
- Email -> userName
- First name / last name
- Department
- Title
- Manager
- Roles or groups
You don’t need to support every SCIM attribute, but you should:
- Accept unknown attributes gracefully
- Document which ones you support
- Provide mapping guidance for each IdP
If you support custom attributes, define them in your schema and be consistent.
12) Decide how groups map to permissions
For Group provisioning, decide whether groups:
- Only sync membership
- Map to roles
- Map to product access levels
- Map to teams/workspaces
A common model:
- SCIM group membership changes update internal role assignments
- A “default role” is assigned on user creation
- Group names map to role codes or entitlements
Be careful with group deletions and renames.
13) Build for idempotency and retries
IdPs retry requests, sometimes due to network issues.
Your provisioning layer should handle:
- Duplicate POSTs
- Repeat PATCHes
- Out-of-order updates
Best practice:
- Use idempotency keys if you can
- Deduplicate by
externalIdand tenant - Treat repeated updates as safe
14) Test against the major IdPs
Test with:
- Okta
- Microsoft Entra ID
- OneLogin
- Ping Identity
- Google Workspace, if relevant
Each has quirks in:
- How they create users
- What PATCH operations they send
- Whether they use
externalId - Group handling
- Username/email expectations
Build a test tenant and validate:
- Create
- Update
- Disable
- Re-enable
- Group sync
- Error handling
15) Operational concerns
You’ll want:
- Audit logs for every SCIM action
- Admin-visible provisioning status
- Webhooks or internal events from SCIM actions
- Monitoring for failed syncs
- Rate limiting protection
- Retry/backoff handling
Also consider:
- How to reconcile drift between IdP and your app
- What happens when a user changes email
- Whether users can be renamed
- Whether provisioning can create duplicates on existing users
16) Recommended minimal roadmap
If you want the shortest path to production:
Phase 1: Users only
- Authenticate requests
POST /UsersGET /Users/{id}GET /Users?filter=...PATCH /Users/{id}foractiveand basic profile changes
Phase 2: Better compatibility
- Add schemas/resource types/service provider config
- Add pagination
- Better error handling
- Support more PATCH operations
Phase 3: Groups
POST/GET/PATCH /Groups- Membership syncing
- Role mapping
Phase 4: Enterprise readiness
- Audit logs
- Admin UI
- Credential rotation
- Connection health checks
- Better provisioning diagnostics
17) Common mistakes to avoid
- Treating SCIM
idas the customer’s ID instead of your own resource ID - Ignoring
externalId - Deleting users instead of deactivating them
- Not handling PATCH
- Not supporting filters
- Not scoping by tenant
- Making
userNamemutable without a clear strategy - Returning non-SCIM error responses
- Not testing with real IdPs
18) A simple data model
A practical internal model might look like:
tenantscim_connectiontenant_idauth_secretbase_url
scim_user_maptenant_idscim_idexternal_idinternal_user_id
users- profile fields
- status/active
groups- name
- role mapping
group_memberships
This keeps SCIM mappings separate from your core user model.
19) If you’re using a framework or library
You can either:
- Implement SCIM yourself
- Use a SCIM library for parsing/serialization
- Put SCIM behind a dedicated provisioning service
If you want less custom protocol work, a separate provisioning service is often easier to maintain.
If you want, I can also give you:
- a reference SCIM 2.0 API design,
- a database schema,
- an implementation example in Node/Python/Java, or
- an Okta/Entra-specific provisioning guide.