Documentation Index
Fetch the complete documentation index at: https://mintlify.com/zitadel/zitadel/llms.txt
Use this file to discover all available pages before exploring further.
The System for Cross-domain Identity Management (SCIM) is a standard for automating the exchange of user identity information between identity domains or IT systems. ZITADEL implements SCIM v2.0, enabling automated user provisioning, deprovisioning, and synchronization.
Why use SCIM?
SCIM provides significant benefits for identity management:
Automated provisioning
Automatically create, update, and deactivate user accounts across systems without manual intervention, reducing administrative overhead and human error.
Standardized protocol
SCIM is an industry standard (RFC 7643, RFC 7644) supported by major identity providers and SaaS applications, ensuring interoperability.
Real-time synchronization
Changes to user accounts are propagated immediately, ensuring consistent identity information across your organization.
Lifecycle management
Manage the complete user lifecycle from onboarding to offboarding, automatically provisioning and deprovisioning access as needed.
Reduced security risks
Promptly remove access when employees leave or change roles, minimizing the risk of unauthorized access.
SCIM Base URL
All SCIM endpoints are available at:
https://${CUSTOM_DOMAIN}/scim/v2/{orgId}
Replace {orgId} with your organization’s ID in ZITADEL.
Authentication
SCIM requests must be authenticated using OAuth 2.0 Bearer tokens. Follow these steps:
- Create a service account in ZITADEL
- Grant the service account necessary permissions for user management
- Obtain an access token using the client credentials flow
- Include the token in the Authorization header
Example authentication:
# Get access token
curl -X POST "https://${CUSTOM_DOMAIN}/oauth/v2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" \
-d "scope=openid profile email"
# Use token in SCIM request
curl -X GET "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Accept: application/scim+json"
Service accounts can be authenticated using the OAuth 2.0 client credentials flow. Create a service account in ZITADEL and generate client credentials to use with the SCIM API.
Supported Endpoints
ZITADEL supports the following SCIM 2.0 endpoints:
Service Provider Configuration
Get service provider configuration:
GET /scim/v2/{orgId}/ServiceProviderConfig
Returns metadata about ZITADEL’s SCIM implementation, including supported features, operations, and schemas.
curl -G "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/ServiceProviderConfig" \
-H "Accept: application/scim+json"
Response:
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
"documentationUri": "https://zitadel.com/docs/guides/manage/user/scim2",
"patch": { "supported": true },
"bulk": {
"supported": true,
"maxOperations": 100,
"maxPayloadSize": 1000000
},
"filter": {
"supported": true,
"maxResults": 100
},
"changePassword": { "supported": true },
"sort": { "supported": true },
"etag": { "supported": false }
}
Schemas
List all schemas:
GET /scim/v2/{orgId}/Schemas
Get specific schema:
GET /scim/v2/{orgId}/Schemas/{id}
Resource Types
List all resource types:
GET /scim/v2/{orgId}/ResourceTypes
Get specific resource type:
GET /scim/v2/{orgId}/ResourceTypes/{name}
User Management
Create User
POST /scim/v2/{orgId}/Users
Minimal user creation:
curl -X POST "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users" \
-H "Content-Type: application/scim+json" \
-H "Accept: application/scim+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-d '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "john.doe",
"name": {
"familyName": "Doe",
"givenName": "John"
},
"password": "SecurePassword123!",
"emails": [
{
"value": "john.doe@example.com",
"primary": true
}
]
}'
Full user creation with all attributes:
curl -X POST "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users" \
-H "Content-Type: application/scim+json" \
-H "Accept: application/scim+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-d '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"externalId": "8d4b51c0-51bd-4386-ae17-79ce5fd36517",
"userName": "john.doe@example.com",
"name": {
"formatted": "Mr. John J Doe, III",
"familyName": "Doe",
"givenName": "John",
"middleName": "Jim",
"honorificPrefix": "Mr.",
"honorificSuffix": "III"
},
"displayName": "John Doe",
"nickName": "Johnny",
"profileUrl": "https://login.example.com/john.doe",
"emails": [
{
"value": "john.doe@example.com",
"type": "work",
"primary": true
}
],
"phoneNumbers": [
{
"value": "+1 555-555-5555",
"type": "work",
"primary": true
}
],
"addresses": [
{
"type": "work",
"streetAddress": "100 Universal City Plaza",
"locality": "Hollywood",
"region": "CA",
"postalCode": "91608",
"country": "USA",
"formatted": "100 Universal City Plaza\nHollywood, CA 91608 USA",
"primary": true
}
],
"userType": "Employee",
"title": "Software Engineer",
"preferredLanguage": "en-US",
"locale": "en-US",
"timezone": "America/Los_Angeles",
"active": true,
"password": "SecurePassword123!"
}'
Retrieve User
GET /scim/v2/{orgId}/Users/{id}
curl -G "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID}" \
-H "Accept: application/scim+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
List Users
GET /scim/v2/{orgId}/Users
Supports filtering, sorting, and pagination.
Basic list:
curl -G "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users" \
-H "Accept: application/scim+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
With pagination:
curl -G "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users" \
-H "Accept: application/scim+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-urlencode "startIndex=1" \
--data-urlencode "count=50"
With filtering and sorting:
curl -G "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users" \
-H "Accept: application/scim+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-urlencode "filter=emails.value eq \"john@example.com\"" \
--data-urlencode "sortBy=name.familyName" \
--data-urlencode "sortOrder=ascending"
Update User (PUT)
PUT /scim/v2/{orgId}/Users/{id}
Replaces the entire user resource.
curl -X PUT "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID}" \
-H "Content-Type: application/scim+json" \
-H "Accept: application/scim+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-d '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "john.doe@example.com",
"name": {
"familyName": "Doe",
"givenName": "John"
},
"emails": [
{
"value": "john.doe@example.com",
"primary": true
}
],
"active": true
}'
Patch User (PATCH)
PATCH /scim/v2/{orgId}/Users/{id}
Partially updates a user resource.
Deactivate user:
curl -X PATCH "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID}" \
-H "Content-Type: application/scim+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "replace",
"path": "active",
"value": false
}
]
}'
Update email:
curl -X PATCH "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID}" \
-H "Content-Type: application/scim+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "replace",
"path": "emails[primary eq true].value",
"value": "newemail@example.com"
}
]
}'
Change password:
curl -X PATCH "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID}" \
-H "Content-Type: application/scim+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "replace",
"path": "password",
"value": "NewSecurePassword456!"
}
]
}'
Delete User
DELETE /scim/v2/{orgId}/Users/{id}
curl -X DELETE "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users/${USER_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
Search Users (POST)
POST /scim/v2/{orgId}/Users/.search
Alternative to GET for complex queries.
curl -X POST "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users/.search" \
-H "Content-Type: application/scim+json" \
-H "Accept: application/scim+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:SearchRequest"],
"filter": "name.familyName eq \"Doe\"",
"sortBy": "name.givenName",
"sortOrder": "ascending",
"startIndex": 1,
"count": 10
}'
Bulk Operations
POST /scim/v2/{orgId}/Bulk
Execute multiple operations in a single request.
curl -X POST "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Bulk" \
-H "Content-Type: application/scim+json" \
-H "Accept: application/scim+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:BulkRequest"],
"Operations": [
{
"method": "POST",
"path": "/Users",
"bulkId": "user1",
"data": {
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "alice@example.com",
"name": {
"familyName": "Smith",
"givenName": "Alice"
},
"emails": [
{
"value": "alice@example.com",
"primary": true
}
]
}
},
{
"method": "PATCH",
"path": "/Users/304499468865155777",
"data": {
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "replace",
"path": "active",
"value": false
}
]
}
},
{
"method": "DELETE",
"path": "/Users/304499603368096449"
}
]
}'
Bulk limits:
- Maximum operations per request: 100
- Maximum payload size: 1,000,000 bytes
Filtering
ZITADEL supports SCIM filtering with the following operators and attributes:
Supported Attributes
| Attribute | Supported Operators |
|---|
meta.created | eq, gt, ge, lt, le |
meta.lastModified | eq, gt, ge, lt, le |
id | eq, ne, co, sw, ew |
externalId | eq, ne |
userName | eq, ne, co, sw, ew |
name.familyName | eq, ne, co, sw, ew |
name.givenName | eq, ne, co, sw, ew |
emails, emails.value | eq, ne, co, sw, ew |
active | eq, ne |
Filter Operators
| Operator | Description | Example |
|---|
eq | Equal | userName eq "john.doe" |
ne | Not equal | active ne false |
co | Contains | emails co "@example.com" |
sw | Starts with | userName sw "john" |
ew | Ends with | emails ew "@example.com" |
gt | Greater than | meta.created gt "2024-01-01T00:00:00Z" |
ge | Greater than or equal | meta.lastModified ge "2024-01-01T00:00:00Z" |
lt | Less than | meta.created lt "2024-12-31T23:59:59Z" |
le | Less than or equal | meta.lastModified le "2024-12-31T23:59:59Z" |
Filter Examples
Find users by email domain:
filter=emails.value ew "@example.com"
Find active users:
Find users created after a date:
filter=meta.created gt "2024-01-01T00:00:00Z"
Complex filter with AND:
filter=active eq true and emails.value ew "@example.com"
Complex filter with OR:
filter=userName sw "john" or name.familyName eq "Doe"
Filter with grouping:
filter=(name.givenName eq "John" or name.givenName eq "Jane") and active eq true
Filter Limitations
- Maximum filter length: 1000 characters
- Nested filters are supported with parentheses
- Attribute paths are case-sensitive
Sorting
Sort results using the sortBy and sortOrder parameters.
Supported Sort Attributes
meta.created
meta.lastModified
id
userName
name.familyName
name.givenName
emails, emails.value
Sort Examples
Sort by family name (ascending):
sortBy=name.familyName&sortOrder=ascending
Sort by creation date (descending):
sortBy=meta.created&sortOrder=descending
Control pagination with startIndex and count parameters.
- Default count: 100 users
- Maximum count: 100 users
- startIndex: 1-based index (first user is 1, not 0)
Example:
curl -G "https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}/Users" \
-H "Accept: application/scim+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-urlencode "startIndex=51" \
--data-urlencode "count=50"
Response includes pagination metadata:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": 150,
"startIndex": 51,
"itemsPerPage": 50,
"Resources": [
// ... user objects
]
}
Error Handling
SCIM errors follow RFC 7644 and include additional ZITADEL-specific information.
Standard SCIM error response:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"status": "400",
"scimType": "invalidValue",
"detail": "The userName field is required"
}
ZITADEL extended error response:
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:Error",
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail"
],
"status": "400",
"scimType": "invalidValue",
"detail": "The userName field is required",
"urn:ietf:params:scim:api:zitadel:messages:2.0:ErrorDetail": {
"id": "SCIM-xy7Hf",
"message": "Errors.User.Username.Missing"
}
}
Common Error Types
| scimType | HTTP Status | Description |
|---|
invalidValue | 400 | Invalid attribute value |
tooMany | 400 | Too many results returned |
uniqueness | 409 | Uniqueness constraint violation |
mutability | 400 | Attempt to modify immutable attribute |
invalidSyntax | 400 | Invalid request syntax |
invalidFilter | 400 | Invalid filter expression |
noTarget | 404 | Resource not found |
Integration Examples
Okta Integration
ZITADEL’s SCIM API can be integrated with Okta and other identity providers for automated user provisioning.
Custom Integration
Basic provisioning workflow:
const axios = require('axios');
const SCIM_BASE = `https://${CUSTOM_DOMAIN}/scim/v2/${ORG_ID}`;
const ACCESS_TOKEN = 'your-access-token';
const headers = {
'Authorization': `Bearer ${ACCESS_TOKEN}`,
'Content-Type': 'application/scim+json',
'Accept': 'application/scim+json'
};
// Create a user
async function createUser(userData) {
const response = await axios.post(
`${SCIM_BASE}/Users`,
{
schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
...userData
},
{ headers }
);
return response.data;
}
// Update a user
async function updateUser(userId, updates) {
const response = await axios.patch(
`${SCIM_BASE}/Users/${userId}`,
{
schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
Operations: updates
},
{ headers }
);
return response.data;
}
// Deactivate a user
async function deactivateUser(userId) {
return updateUser(userId, [
{ op: 'replace', path: 'active', value: false }
]);
}
// Search users
async function searchUsers(filter) {
const response = await axios.get(
`${SCIM_BASE}/Users`,
{
headers,
params: { filter }
}
);
return response.data;
}
// Example usage
const newUser = await createUser({
userName: 'jane.doe',
name: {
familyName: 'Doe',
givenName: 'Jane'
},
emails: [
{ value: 'jane.doe@example.com', primary: true }
],
password: 'SecurePassword123!'
});
console.log('Created user:', newUser.id);
// Update the user
await updateUser(newUser.id, [
{ op: 'replace', path: 'title', value: 'Senior Engineer' }
]);
// Search for users
const results = await searchUsers('emails.value ew "@example.com"');
console.log(`Found ${results.totalResults} users`);
Best Practices
- Use bulk operations: Reduce API calls by batching operations
- Implement retry logic: Handle rate limits and transient errors
- Filter efficiently: Use specific filters to reduce response size
- Paginate results: Don’t request all users at once
- Monitor quota: Track API usage to stay within limits
- Use PATCH for updates: More efficient than PUT for partial updates
- Validate before sending: Check data locally before API calls
- Handle errors gracefully: Implement proper error handling and logging
- Cache access tokens: Reuse tokens until they expire
- Test thoroughly: Use ServiceProviderConfig to understand capabilities
Limitations
- Maximum 100 users per request
- Maximum 100 operations per bulk request
- Maximum 1MB bulk request payload
- Maximum 1000 character filter length
- ETags are not supported
- Group resources are not currently supported
Resources