SCIM Protocol
When a new employee joins a company, their accounts need to be created in every application the team uses: email, chat, project management, source control, and more. When they leave, those accounts need to be deactivated everywhere. Doing this manually is slow, error-prone, and does not scale. SCIM (System for Cross-domain Identity Management) is a protocol designed to automate this process.
What Problem Does SCIM Solve
Enterprise organizations use dozens of SaaS applications. Each application maintains its own user directory. Without automation, IT administrators must:
- Manually create user accounts in every application when someone joins
- Update user attributes (name, department, role) across all systems when they change
- Deactivate accounts in every application when someone leaves
- Keep group memberships in sync across all platforms
This manual process leads to security risks (orphaned accounts that should have been deactivated), delays (new hires waiting days for access), and inconsistencies (different names or roles across systems).
SCIM standardizes how identity data is created, read, updated, and deleted across systems. It defines a REST API, a JSON schema for users and groups, and a set of operations that any SCIM-compliant system can understand.
How SCIM Works
SCIM follows a client-server model:
- SCIM Client (Identity Provider): the source of truth for user data. Typically an IdP like Okta, Azure AD, or OneLogin
- SCIM Server (Service Provider): the application that receives and applies user provisioning requests. Any SaaS app that supports SCIM
The IdP pushes identity changes to each application via SCIM API calls.
sequenceDiagram
participant Admin as IT Admin
participant IdP as Identity Provider
participant App1 as Application 1
participant App2 as Application 2
Admin->>IdP: 1. Create user "John Doe"
IdP->>App1: 2. POST /Users (create John)
App1-->>IdP: 3. 201 Created
IdP->>App2: 4. POST /Users (create John)
App2-->>IdP: 5. 201 Created
Admin->>IdP: 6. Update John's department
IdP->>App1: 7. PATCH /Users/{id}
App1-->>IdP: 8. 200 OK
IdP->>App2: 9. PATCH /Users/{id}
App2-->>IdP: 10. 200 OK
Admin->>IdP: 11. Deactivate John
IdP->>App1: 12. PATCH /Users/{id} (active=false)
App1-->>IdP: 13. 200 OK
IdP->>App2: 14. PATCH /Users/{id} (active=false)
App2-->>IdP: 15. 200 OK
The IdP handles the orchestration. The administrator makes a single change in the IdP, and SCIM propagates it to all connected applications automatically.
SCIM Protocol Basics
SCIM is built on standard HTTP and JSON. It defines two versions: SCIM 1.1 and SCIM 2.0. SCIM 2.0 (RFC 7642, RFC 7643, RFC 7644) is the current standard and the one covered here.
Base URL
All SCIM endpoints live under a base URL:
https://app.example.com/scim/v2
Resources
SCIM defines two core resource types:
| Resource | Endpoint | Purpose |
|---|---|---|
| User | /Users | Individual user accounts |
| Group | /Groups | Collections of users |
HTTP Methods
SCIM uses standard HTTP methods mapped to CRUD operations:
| Method | Operation | Example |
|---|---|---|
POST | Create | POST /Users |
GET | Read | GET /Users/{id} |
PUT | Replace | PUT /Users/{id} |
PATCH | Partial update | PATCH /Users/{id} |
DELETE | Delete | DELETE /Users/{id} |
Authentication
SCIM itself does not define a specific authentication mechanism. The RFC recommends OAuth2 Bearer tokens, but in practice the most common methods are:
- Bearer token: a long-lived API token generated by the application and configured in the IdP
- HTTP Basic Auth: username and password sent in the
Authorizationheader - OAuth2 Client Credentials: the IdP obtains a short-lived token via the OAuth2 client credentials flow
Bearer tokens are the most widely supported option across SaaS applications.
All requests and responses use the application/scim+json content type.
GET /scim/v2/Users HTTP/1.1
Host: app.example.com
Authorization: Bearer <token>
Accept: application/scim+json
Or with Basic Auth:
GET /scim/v2/Users HTTP/1.1
Host: app.example.com
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Accept: application/scim+json
User Schema
SCIM defines a standard JSON schema for user resources. The core schema uses the
URI urn:ietf:params:scim:schemas:core:2.0:User.
Create User Request
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "john.doe@example.com",
"name": {
"givenName": "John",
"familyName": "Doe"
},
"emails": [
{
"primary": true,
"value": "john.doe@example.com",
"type": "work"
}
],
"displayName": "John Doe",
"active": true,
"title": "Software Engineer",
"department": "Engineering"
}
Create User Response
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "2819c223-7f76-453a-919d-413861904646",
"userName": "john.doe@example.com",
"name": {
"givenName": "John",
"familyName": "Doe"
},
"emails": [
{
"primary": true,
"value": "john.doe@example.com",
"type": "work"
}
],
"displayName": "John Doe",
"active": true,
"title": "Software Engineer",
"department": "Engineering",
"meta": {
"resourceType": "User",
"created": "2026-02-12T10:00:00Z",
"lastModified": "2026-02-12T10:00:00Z",
"location": "https://app.example.com/scim/v2/Users/2819c223-7f76-453a-919d-413861904646"
}
}
The meta object is added by the server. It contains the resource type, timestamps,
and the canonical URL for this resource.
Core User Attributes
| Attribute | Type | Description |
|---|---|---|
id | String | Unique identifier assigned by the server |
userName | String | Unique login name, usually an email |
name.givenName | String | First name |
name.familyName | String | Last name |
displayName | String | Human-readable name |
emails | Array | Email addresses with type and primary flag |
active | Boolean | Whether the account is enabled |
title | String | Job title |
department | String | Organizational department |
phoneNumbers | Array | Phone numbers with type |
groups | Array | Groups the user belongs to (read-only) |
Group Schema
Groups represent collections of users. The core schema uses the
URI urn:ietf:params:scim:schemas:core:2.0:Group.
Create Group Request
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"displayName": "Engineering",
"members": [
{
"value": "2819c223-7f76-453a-919d-413861904646",
"display": "John Doe"
}
]
}
Group Response
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
"displayName": "Engineering",
"members": [
{
"value": "2819c223-7f76-453a-919d-413861904646",
"display": "John Doe",
"$ref": "https://app.example.com/scim/v2/Users/2819c223-7f76-453a-919d-413861904646"
}
],
"meta": {
"resourceType": "Group",
"created": "2026-02-12T10:00:00Z",
"lastModified": "2026-02-12T10:00:00Z"
}
}
Common Operations
Updating a User (PATCH)
SCIM PATCH uses a specific format defined in RFC 7644. Each operation specifies what to change:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "replace",
"path": "title",
"value": "Senior Software Engineer"
},
{
"op": "replace",
"path": "department",
"value": "Platform Engineering"
}
]
}
Supported PATCH operations:
| Operation | Purpose | Example |
|---|---|---|
add | Add a new attribute or value | Add a phone number |
replace | Update an existing attribute | Change department |
remove | Delete an attribute or value | Remove a phone number |
Deactivating a User
SCIM does not typically delete users. Instead, the active attribute is set to false.
This preserves the user record for audit purposes while revoking access:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "replace",
"path": "active",
"value": false
}
]
}
Listing Users with Filtering
SCIM supports filtering to search for specific users:
GET /scim/v2/Users?filter=userName eq "john.doe@example.com" HTTP/1.1
Host: app.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Accept: application/scim+json
The response includes a list of matching resources:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": 1,
"startIndex": 1,
"itemsPerPage": 1,
"Resources": [
{
"id": "2819c223-7f76-453a-919d-413861904646",
"userName": "john.doe@example.com",
"displayName": "John Doe",
"active": true
}
]
}
Common filter operators:
| Operator | Meaning | Example |
|---|---|---|
eq | Equals | userName eq "john@example.com" |
ne | Not equals | active ne false |
co | Contains | displayName co "John" |
sw | Starts with | userName sw "john" |
and | Logical AND | active eq true and department eq "Engineering" |
or | Logical OR | department eq "Engineering" or department eq "Design" |
Pagination
Large directories require pagination. SCIM uses startIndex and count parameters:
GET /scim/v2/Users?startIndex=1&count=100 HTTP/1.1
Host: app.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Accept: application/scim+json
The response includes totalResults, startIndex, and itemsPerPage so the client
can calculate how many pages remain.
Provisioning Flow
A typical SCIM provisioning flow involves initial synchronization followed by incremental updates.
Initial Sync
When an application is first connected to an IdP, the IdP performs a full sync:
sequenceDiagram
participant IdP as Identity Provider
participant App as Application
IdP->>App: 1. GET /Users?filter=userName eq "john@example.com"
App-->>IdP: 2. 200 OK (totalResults: 0)
Note over IdP: User does not exist
IdP->>App: 3. POST /Users (create John)
App-->>IdP: 4. 201 Created (id: abc-123)
Note over IdP: Store mapping: IdP user -> abc-123
IdP->>App: 5. GET /Users?filter=userName eq "jane@example.com"
App-->>IdP: 6. 200 OK (totalResults: 1, id: def-456)
Note over IdP: User already exists
IdP->>App: 7. PUT /Users/def-456 (update Jane)
App-->>IdP: 8. 200 OK
The IdP checks if each user already exists (via filter), creates missing users, and updates existing ones.
Incremental Updates
After the initial sync, the IdP sends changes as they happen:
sequenceDiagram
participant HR as HR System
participant IdP as Identity Provider
participant App1 as Application 1
participant App2 as Application 2
HR->>IdP: 1. Employee promoted
IdP->>App1: 2. PATCH /Users/{id} (new title)
App1-->>IdP: 3. 200 OK
IdP->>App2: 4. PATCH /Users/{id} (new title)
App2-->>IdP: 5. 200 OK
HR->>IdP: 6. Employee leaves company
IdP->>App1: 7. PATCH /Users/{id} (active=false)
App1-->>IdP: 8. 200 OK
IdP->>App2: 9. PATCH /Users/{id} (active=false)
App2-->>IdP: 10. 200 OK
SCIM vs Manual Provisioning
| Manual | SCIM | |
|---|---|---|
| User creation | Admin creates in each app | Automatic from IdP |
| User updates | Admin updates in each app | Automatic propagation |
| Deprovisioning | Admin disables in each app | Automatic deactivation |
| Time to access | Hours to days | Minutes |
| Orphaned accounts | Common | Eliminated |
| Consistency | Error-prone | Guaranteed by protocol |
| Audit trail | Fragmented across apps | Centralized in IdP |
| Scalability | Does not scale | Scales to thousands of apps |
SCIM and SSO: Complementary Protocols
SCIM is often confused with SAML or OIDC, but they solve different problems. SSO protocols (SAML, OIDC) handle authentication: proving who a user is at login time. SCIM handles provisioning: managing the lifecycle of user accounts before and after login.
flowchart LR
subgraph Provisioning
A[HR System] --> B[Identity Provider]
B -->|SCIM| C[Application]
end
subgraph Authentication
D[User] -->|SAML / OIDC| E[Identity Provider]
E --> F[Application]
end
A typical enterprise setup uses both:
- SCIM creates and manages the user account in the application
- SAML or OIDC authenticates the user when they log in
- SCIM deactivates the account when the user leaves
Without SCIM, SSO alone cannot create accounts. Users would need to be manually provisioned before they could use SSO to log in. Without SSO, SCIM alone cannot authenticate users. Both protocols are needed for a complete identity lifecycle.
Error Handling
SCIM uses standard HTTP status codes with a JSON error response body:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"status": "409",
"scimType": "uniqueness",
"detail": "userName already exists"
}
Common error responses:
| Status | Meaning | Typical Cause |
|---|---|---|
| 400 | Bad Request | Malformed JSON or invalid attribute |
| 401 | Unauthorized | Missing or invalid Bearer token |
| 404 | Not Found | User or group ID does not exist |
| 409 | Conflict | Duplicate userName or other uniqueness violation |
| 413 | Payload Too Large | Bulk request exceeds server limits |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server-side failure |
Summary
- SCIM is a REST-based protocol for automating user provisioning and deprovisioning across applications
- It defines a standard JSON schema for Users and Groups with CRUD operations mapped to HTTP methods
- The IdP acts as the SCIM client, pushing changes to applications (SCIM servers)
- SCIM complements SSO protocols: SCIM manages account lifecycle, SAML/OIDC handles authentication
- Deactivation (
active=false) is preferred over deletion to preserve audit records - SCIM 2.0 (RFC 7642, RFC 7643, RFC 7644) is the current standard