SCIM 2.0 API Reference
Base URL:
https://auth.calimatic.com/api/v1/scim/v2System for Cross-domain Identity Management (SCIM) 2.0 endpoints for automated user provisioning and deprovisioning.
Prerequisites
- An organization on an Enterprise plan (SCIM is a plan-gated feature; gating is enforced by the calling app)
- Either an org-scoped API key for that organization (preferred for customer IdP callers) or app-client credentials with the target org passed as
?org=<orgId> - An identity provider that supports SCIM 2.0 (e.g., Azure AD / Entra ID, Okta, OneLogin, JumpCloud)
Overview
SCIM provides a standardized protocol for identity providers to automatically:
- Create users when they are assigned to your application in the IdP
- Update user profiles when changes are made in the IdP
- Deactivate users when they are unassigned or removed from the IdP
- Manage groups for role-based access control
Supported SCIM Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET | /scim/v2/ServiceProviderConfig | SCIM service provider capabilities |
GET | /scim/v2/Schemas | Supported SCIM schemas |
GET | /scim/v2/ResourceTypes | Available resource types |
GET | /scim/v2/Users | List/search users |
GET | /scim/v2/Users/{id} | Get a single user |
POST | /scim/v2/Users | Create a user |
PUT | /scim/v2/Users/{id} | Replace a user |
PATCH | /scim/v2/Users/{id} | Update specific user attributes |
DELETE | /scim/v2/Users/{id} | Delete a user |
GET | /scim/v2/Groups | List/search groups |
GET | /scim/v2/Groups/{id} | Get a single group |
POST | /scim/v2/Groups | Create a group |
PUT | /scim/v2/Groups/{id} | Replace a group |
PATCH | /scim/v2/Groups/{id} | Update group membership |
DELETE | /scim/v2/Groups/{id} | Delete a group |
Groups note: SCIM Groups map to Calimatic organizations. Because a tenant-scoped caller only has visibility into a single org, GET /Groups returns exactly one row (the caller's own org) and GET /Groups/{id} returns 404 for any id other than the caller's own org id. POST /Groups is not supported (501) — organizations are provisioned through the platform admin API, not by a customer's IdP.
Authentication and Tenant Scoping
Every SCIM data request is scoped to exactly one organization. The tenant is derived from the caller and cannot be widened by query params. Reads never return data from other orgs; writes reject targets in other orgs with 404 Not Found.
Three ways to authenticate:
| Method | Header(s) | Tenant Resolution |
|---|---|---|
| Bearer token (API key) | Authorization: Bearer <api_key> | Org is derived from the API key. If ?org= is also present and it doesn't match, request is rejected 403. |
| x-api-key header | x-api-key: <api_key> | Same as Bearer. |
| App-client credentials | x-client-id: cca_... + x-client-secret: ccas_... | ?org=<orgId> is required on every request. 400 if missing. |
Recommended shape for a customer's own IdP connecting to Calimatic: issue an org-scoped API key from the org's admin dashboard and hand it to the IdP. The tenant scope is then baked into the credential and can't be tampered with by URL manipulation.
App-client credentials are the shape a Calimatic app uses when calling SCIM on behalf of one of its customers (e.g. the org-admin UI in the app makes a SCIM read/write). The app passes ?org=<orgId> for the customer it's acting on.
Obtaining an org-scoped API key
Three shapes, pick whichever matches the caller:
- Platform UI: Developer → API Keys → Create key (session must belong to the target org).
- Session-authed API:
POST /api/v1/api-keys— org is derived from the session; returns the raw key once. - App-client-authed API (server-side proxy):
POST /api/v1/api-keys?org=<orgId>withx-client-id+x-client-secret. Requiresorg:users:manageon the app-client. This is the shape a Calimatic app uses when rendering a "Generate SCIM key" button in its own admin UI on behalf of a customer's org admin.
The raw key is only shown in the response body — store it securely on the client side; there is no way to retrieve it later. userIdentityId on the resulting key is null when issued by an app-client; the audit log records the app-client attribution separately.
Breaking change (2026-09)
Prior to this update, SCIM endpoints did not enforce per-tenant scoping — a caller with any valid credential could read/write users across all organizations. That surface has been closed. If you had callers relying on cross-tenant reads, they must now iterate one call per org (using either an org-scoped key per org, or app-client credentials with the appropriate ?org= per call).
ServiceProviderConfig
GET /scim/v2/ServiceProviderConfig
Returns the SCIM service provider configuration document describing supported features.
Response
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
"documentationUri": "https://auth.calimatic.com/docs/api-reference/scim",
"patch": { "supported": true },
"bulk": { "supported": false, "maxOperations": 0, "maxPayloadSize": 0 },
"filter": { "supported": true, "maxResults": 100 },
"changePassword": { "supported": false },
"sort": { "supported": false },
"etag": { "supported": false },
"authenticationSchemes": [
{
"type": "oauthbearertoken",
"name": "OAuth Bearer Token",
"description": "Authentication using a Bearer token",
"specUri": "https://tools.ietf.org/html/rfc6750"
}
]
}
Schemas
GET /scim/v2/Schemas
Returns the list of supported SCIM schemas.
ResourceTypes
GET /scim/v2/ResourceTypes
Returns the resource types supported by this SCIM server (Users and Groups).
Users
User Schema Mapping
| SCIM Attribute | Calimatic Auth Field | Type | Required |
|---|---|---|---|
userName | email | string | Yes |
name.givenName | firstName | string | Yes |
name.familyName | lastName | string | Yes |
name.formatted | displayName | string | No |
emails[0].value | email | string | Yes |
emails[0].primary | -- | boolean | No |
active | status (active/deactivated) | boolean | No |
externalId | externalId (metadata) | string | No |
displayName | displayName | string | No |
phoneNumbers[0].value | phone | string | No |
locale | locale | string | No |
timezone | timezone | string | No |
photos[0].value | avatarUrl | string | No |
List Users
GET /scim/v2/Users
Query Parameters
| Parameter | Type | Description |
|---|---|---|
filter | string | SCIM filter expression (e.g., userName eq "jane@example.com") |
startIndex | number | 1-based starting index for pagination (default: 1) |
count | number | Number of results per page (default: 20, max: 100) |
Supported Filter Operators
| Operator | Example |
|---|---|
eq | userName eq "jane@example.com" |
co | name.familyName co "Smi" |
sw | userName sw "jane" |
and | active eq true and name.familyName eq "Smith" |
Example Request
curl "https://auth.calimatic.com/scim/v2/Users?filter=userName%20eq%20%22jane%40example.com%22&count=10" \
-H "Authorization: Bearer scim_your_token"
Response
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": 1,
"startIndex": 1,
"itemsPerPage": 10,
"Resources": [
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "f1g2h3i4-j5k6-7890-lmno-pq1234567890",
"externalId": "usr_12345",
"userName": "jane@example.com",
"name": {
"givenName": "Jane",
"familyName": "Smith",
"formatted": "Jane Smith"
},
"displayName": "Jane Smith",
"emails": [
{
"value": "jane@example.com",
"type": "work",
"primary": true
}
],
"active": true,
"meta": {
"resourceType": "User",
"created": "2025-01-10T10:00:00.000Z",
"lastModified": "2025-01-15T14:30:00.000Z",
"location": "https://auth.calimatic.com/scim/v2/Users/f1g2h3i4-j5k6-7890-lmno-pq1234567890"
}
}
]
}
Get a Single User
GET /scim/v2/Users/{id}
Returns the full SCIM User resource.
Create a User
POST /scim/v2/Users
Content-Type: application/scim+json
Example Request
curl -X POST https://auth.calimatic.com/scim/v2/Users \
-H "Authorization: Bearer scim_your_token" \
-H "Content-Type: application/scim+json" \
-d '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "bob@example.com",
"name": {
"givenName": "Bob",
"familyName": "Jones"
},
"emails": [
{
"value": "bob@example.com",
"type": "work",
"primary": true
}
],
"active": true,
"externalId": "okta_user_12345"
}'
Response (201 Created)
Returns the created SCIM User resource with the id assigned by Calimatic Auth.
Replace a User
PUT /scim/v2/Users/{id}
Content-Type: application/scim+json
Replaces all user attributes with the provided values. Attributes not included are cleared to defaults.
Update a User (PATCH)
PATCH /scim/v2/Users/{id}
Content-Type: application/scim+json
Update specific attributes without replacing the entire resource.
Example: Deactivate a User
curl -X PATCH https://auth.calimatic.com/scim/v2/Users/f1g2h3i4-j5k6-7890 \
-H "Authorization: Bearer scim_your_token" \
-H "Content-Type: application/scim+json" \
-d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "Replace",
"path": "active",
"value": false
}
]
}'
Example: Update Name
curl -X PATCH https://auth.calimatic.com/scim/v2/Users/f1g2h3i4-j5k6-7890 \
-H "Authorization: Bearer scim_your_token" \
-H "Content-Type: application/scim+json" \
-d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "Replace",
"path": "name.givenName",
"value": "Janet"
}
]
}'
Delete a User
DELETE /scim/v2/Users/{id}
Deactivates the user in Calimatic Auth. Returns 204 No Content.
Groups
SCIM Groups map to roles within a Calimatic organization. Assigning a user to a SCIM Group grants them the corresponding role.
List Groups
GET /scim/v2/Groups
Query Parameters
| Parameter | Type | Description |
|---|---|---|
filter | string | SCIM filter (e.g., displayName eq "Admins") |
startIndex | number | 1-based start index |
count | number | Results per page |
Example Response
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": 3,
"Resources": [
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"id": "grp-uuid-...",
"displayName": "Admins",
"members": [
{ "value": "user-uuid-1", "display": "Jane Smith" },
{ "value": "user-uuid-2", "display": "Bob Jones" }
],
"meta": {
"resourceType": "Group",
"created": "2025-01-10T10:00:00.000Z",
"lastModified": "2025-01-15T14:30:00.000Z"
}
}
]
}
Update Group Membership (PATCH)
PATCH /scim/v2/Groups/{id}
Content-Type: application/scim+json
Example: Add a Member
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "Add",
"path": "members",
"value": [
{ "value": "user-uuid-..." }
]
}
]
}
Example: Remove a Member
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "Remove",
"path": "members[value eq \"user-uuid-...\"]"
}
]
}
Error Responses
SCIM errors follow the standard SCIM error schema:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"status": "404",
"detail": "User not found"
}
Status Codes
| Status | Description |
|---|---|
| 200 | Success (GET, PATCH, PUT) |
| 201 | Created (POST) |
| 204 | No Content (DELETE) |
| 400 | Bad request (invalid filter, missing required fields) |
| 401 | Unauthorized (missing or invalid Bearer token) |
| 404 | Resource not found |
| 409 | Conflict (user with this email already exists) |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
Configuring Your Identity Provider
Azure AD / Entra ID
- Go to Enterprise Applications > Your App > Provisioning
- Set Provisioning Mode to Automatic
- Enter:
- Tenant URL:
https://auth.calimatic.com/scim/v2 - Secret Token: Your SCIM Bearer token
- Tenant URL:
- Click Test Connection
- Configure attribute mappings (see User Schema Mapping)
- Enable provisioning
Okta
- Go to Applications > Your App > Provisioning > Integration
- Enable SCIM connector
- Enter:
- SCIM connector base URL:
https://auth.calimatic.com/scim/v2 - Authentication Mode: HTTP Header
- Authorization:
Bearer scim_your_token
- SCIM connector base URL:
- Configure supported operations (Create, Update, Deactivate)
- Configure attribute mappings
OneLogin
- Go to Applications > Your App > Provisioning
- Enable provisioning
- Enter the SCIM base URL and Bearer token
- Map attributes as needed
Rate Limits
| Endpoint | Limit | Window |
|---|---|---|
| All SCIM endpoints | 60 requests | 1 minute per token |
When rate limited, the response includes a Retry-After header.