Skip to main content Skip to sidebar

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:

ResourceEndpointPurpose
User/UsersIndividual user accounts
Group/GroupsCollections of users

HTTP Methods

SCIM uses standard HTTP methods mapped to CRUD operations:

MethodOperationExample
POSTCreatePOST /Users
GETReadGET /Users/{id}
PUTReplacePUT /Users/{id}
PATCHPartial updatePATCH /Users/{id}
DELETEDeleteDELETE /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 Authorization header
  • 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

AttributeTypeDescription
idStringUnique identifier assigned by the server
userNameStringUnique login name, usually an email
name.givenNameStringFirst name
name.familyNameStringLast name
displayNameStringHuman-readable name
emailsArrayEmail addresses with type and primary flag
activeBooleanWhether the account is enabled
titleStringJob title
departmentStringOrganizational department
phoneNumbersArrayPhone numbers with type
groupsArrayGroups 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:

OperationPurposeExample
addAdd a new attribute or valueAdd a phone number
replaceUpdate an existing attributeChange department
removeDelete an attribute or valueRemove 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:

OperatorMeaningExample
eqEqualsuserName eq "john@example.com"
neNot equalsactive ne false
coContainsdisplayName co "John"
swStarts withuserName sw "john"
andLogical ANDactive eq true and department eq "Engineering"
orLogical ORdepartment 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

ManualSCIM
User creationAdmin creates in each appAutomatic from IdP
User updatesAdmin updates in each appAutomatic propagation
DeprovisioningAdmin disables in each appAutomatic deactivation
Time to accessHours to daysMinutes
Orphaned accountsCommonEliminated
ConsistencyError-proneGuaranteed by protocol
Audit trailFragmented across appsCentralized in IdP
ScalabilityDoes not scaleScales 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:

  1. SCIM creates and manages the user account in the application
  2. SAML or OIDC authenticates the user when they log in
  3. 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:

StatusMeaningTypical Cause
400Bad RequestMalformed JSON or invalid attribute
401UnauthorizedMissing or invalid Bearer token
404Not FoundUser or group ID does not exist
409ConflictDuplicate userName or other uniqueness violation
413Payload Too LargeBulk request exceeds server limits
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-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