SCIM API
The SCIM v2 base path is /api/v1/scim/v2. Send the one-time credential created on Admin > Auth / SSO with bearer authentication. Session cookies and application API keys are not accepted on this path. Requests may use application/scim+json or application/json; responses use application/scim+json.
Every request receives a correlation id and passes the SCIM request limit before a JSON body is read. A request over that limit returns 429; otherwise, a missing or inactive SCIM credential returns 401 before body parsing. Credential rotation may keep the previous value active for an administrator-selected overlap of 0 to 24 hours. Validation reads the database for every request, so overlap end and revocation apply across application processes. After authentication, malformed JSON sent with a supported content type returns 400 invalidSyntax, and a body over 256 KB returns a SCIM 413 error. Both carry a correlation id and appear in Operations diagnostics.
Setup steps for Okta and Entra ID, group mappings, and troubleshooting are in SCIM provisioning.
Try the endpoint
Section titled “Try the endpoint”These requests use the example origin https://cuttlely.example.com and keep the credential in the CUTTLELY_SCIM_TOKEN environment variable of your shell. Use the base URL shown on the SCIM card.
# Discovery: no users are touched.curl -s https://cuttlely.example.com/api/v1/scim/v2/ServiceProviderConfig \ -H "Authorization: Bearer $CUTTLELY_SCIM_TOKEN"
# The connection probe identity providers send. An unknown userName returns an empty list.curl -s -G https://cuttlely.example.com/api/v1/scim/v2/Users \ --data-urlencode 'filter=userName eq "bob@example.com"' \ -H "Authorization: Bearer $CUTTLELY_SCIM_TOKEN"
# Create a user. The response is 201 with the stable SCIM resource id.curl -s https://cuttlely.example.com/api/v1/scim/v2/Users \ -H "Authorization: Bearer $CUTTLELY_SCIM_TOKEN" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "userName": "bob@example.com", "externalId": "ext-1001", "name": { "givenName": "Bob", "familyName": "Example" }, "emails": [{ "value": "bob@example.com", "type": "work", "primary": true }], "active": true }'
# Deactivate it the way Entra ID does. First set CUTTLELY_SCIM_USER_ID to the id from the create response.curl -s -X PATCH "https://cuttlely.example.com/api/v1/scim/v2/Users/$CUTTLELY_SCIM_USER_ID" \ -H "Authorization: Bearer $CUTTLELY_SCIM_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" }] }'Each request appears in the Operations section of the SCIM card. Deactivation ends that user’s sessions and disables the API credentials and personal tokens they own. API credentials created before 1.4.9 have no recorded owner and stay usable; see Sessions and account status.
Discovery
Section titled “Discovery”GET /ServiceProviderConfigreports PATCH and filtering, a maximum list count of 100, no bulk, no password change, no sorting, and no ETags.GET /ResourceTypes,/ResourceTypes/User, and/ResourceTypes/Groupdescribe the resources.GET /Schemasand the individual User and Group schema paths describe the supported core attributes./Bulkreturns a standards-shaped501error. Sorting and ETags are not supported.
POST /Userscreates or cautiously links a user.userNameis an email address.externalId,displayName,name.givenName,name.familyName, a work email, andactiveare supported.GET /Users/{id}reads the stable SCIM resource id. The internal user id and email are never used as that resource id.GET /UsersacceptsstartIndex,count,attributes,excludedAttributes, and a filter. The supported grammar iseqwithand,or, and parentheses onuserName,externalId, anddisplayName. Attribute names,userName, anddisplayNamecomparisons are case-insensitive;externalIdcomparisons are case-sensitive on every supported database. Results have stable order andcountis at most 100.PUT /Users/{id}replaces supported values.active: falsedeactivates the account.PATCH /Users/{id}accepts case-insensitive Add, Replace, and Remove operations. Supported paths areactive,userName,externalId,displayName,name.givenName,name.familyName, andemails[type eq "work"].value. The core User schema URN may prefix a supported path. A well-formed path is an attribute name, optionally prefixed by a schema URN and optionally followed by.subAttr, or an attribute with a simple bracketed filter and optional.subAttr. Well-formed unsupported paths, including Entra ID’s defaulttitleand enterprise-extension mappings, are ignored;id,password,roles, and a malformedpathvalue are refused. Pathless Add and Replace merge only the attributes present invalue; omitted attributes remain unchanged, other keys invalue, including malformed ones, are ignored, and the same three immutable attributes are refused there. Case-insensitive path entries invalue, includingname.givenName,emails[type eq "work"].value, andactive, are accepted for Entra ID.activeaccepts boolean or case-insensitive string values.DELETE /Users/{id}returns204, deactivates the internal account, removes its group memberships, reconciles mapped assignments, and removes the resource from later GET, list, and filter results.
Creation never grants the owner role. SCIM can provision a new account or link one unlinked, non-owner account that is already a member of the target organization and has no membership in any other organization. Like an ordinary invitation, it does not move or duplicate a global account that belongs to another organization: POST /Users returns 409 uniqueness with no details about that account. SCIM also refuses updates, reactivation, deletion, and recreation for an account that has gained another organization membership. If a deleted SCIM resource is created again with the same userName while its sole organization membership remains the target organization, Cuttlely restores its stable resource id and account. A new userName already used by another account is refused with 409 uniqueness. No previous session, API key, or personal token is restored.
All deactivation forms disable the user globally, close every open invite and reset link for that user across organizations, advance the authentication version, delete sessions, and disable owned API keys and personal tokens in one transaction. Before that global disable, Cuttlely locks and checks every organization membership. Deactivation never leaves any organization without an active owner. The response is 409 and the transaction makes no account, role, session, or credential change.
When SCIM provisioning is in use, the identity provider is authoritative for a provisioned user’s active state: a manual disable in Cuttlely is reversed by the next provider update that sends active: true, so disable or unassign the user in the identity provider instead.
SCIM deactivation also changes a linked pending account to disabled. Administrator disable preserves a pending account’s invited status while closing its membership and invite links; saving it as invited reopens the membership without activating the account or restoring an old invite link.
Groups
Section titled “Groups”POST /Groupscreates a provider group with requireddisplayName, optionalexternalId, and an optionalmembersarray.displayNameis trimmed and unique per organization without regard to letter case. If the name or external id matches a deleted group, the request restores that stable group id and replaces its members with the requested set. A match with a current group returns409 uniqueness.GET /Groups/{id}reads a stable group id.GET /GroupsacceptsstartIndex,count,attributes,excludedAttributes, andeqfilters joined byand,or, and parentheses ondisplayNameandexternalId.displayNamecomparison is case-insensitive andexternalIdcomparison is case-sensitive on every supported database.excludedAttributes=membersomits the member list.PUT /Groups/{id}replaces the supported attributes and complete membership.PATCH /Groups/{id}accepts case-insensitive Add, Replace, and Remove operations. Members may be added, removed, or replaced with value arrays. Remove also acceptsmembers[value eq "id"]; Remove onmemberswithout a value removes every member and uses the same mapping reconciliation as a full empty replacement. Pathless Add and Replace merge onlydisplayName,externalId, ormembersattributes present invalue; omitted attributes remain unchanged. When one request contains several membership operations, they are applied sequentially in request order before one reconciliation.- Member additions and full replacements require current SCIM User ids. An unknown or deleted id returns
400 invalidValueand the request makes no partial change. Removing an unknown or already-deleted id succeeds as a no-op. DELETE /Groups/{id}returns 204, removes the group from later reads, and transactionally removes assignments owned by its mapping.
Group storage and membership are provider-scoped and independent of Cuttlely roles until an administrator creates a mapping. A mapping connects one group to one team and optionally one allowed role. Roles with authentication:manage and the owner role are refused at creation and checked again during every reconcile. Reconciliation creates and changes only assignments in its ownership ledger. It does not take over a manual membership, change an owner, or touch unrelated teams. Conflicting role requests for one user and team remove mapping-owned membership for that pair, preserve independently changed membership, and create a diagnostic.
Ordinary administration cannot change a mapped role into an Owner or add authentication:manage, delete a mapped role, or delete a mapped team. Each returns 409; remove the mapping with its preview and confirmed removal first. Direct organization removal of a person with a current SCIM link also returns 409. Delete the person in the identity provider or remove the SCIM link before using ordinary user removal. A SCIM-deleted link no longer blocks ordinary removal. That removal discards the deleted SCIM resource, its provider-group memberships, and its mapping-owned assignments. A later POST /Users provisions a new resource id only when the identity is new or is already a member of this organization; an account that still belongs to another organization returns 409 uniqueness. Reprovisioning does not restore previous group memberships or mapped team access.
Mapping creation uses POST /api/v1/authentication-settings/scim-mappings/preview to return the selected group, team, role, assignments to add, manual memberships, conflicts, and a fingerprint. POST /api/v1/authentication-settings/scim-mappings/confirm recomputes that preview and requires its fingerprint before creating the mapping. Mapping removal uses GET /api/v1/authentication-settings/scim-mappings/{id}/remove-preview and POST /api/v1/authentication-settings/scim-mappings/{id}/remove in the same way. A changed fingerprint returns 409 without applying the change. These routes remain behind normal Admin authentication and authentication:manage; confirmation also requires a fresh sign-in.
Group membership changes, group deletion, mapping confirmation, and user deactivation, reactivation, or recreation reconcile in the triggering transaction and advance permission versions for affected users. User deletion removes provider group memberships, so recreation does not restore mapped access unless the provider adds those memberships again. Group audits contain only the group id, operation, and member count. Mapping changes and refused last-owner changes also use sanitized audit entries.
Operations
Section titled “Operations”The Auth / SSO page shows credential state, last successful, failed, and completed request times, the last failed category, operation counts, and up to 20 recent sanitized errors. Counts and timestamps use database aggregates scoped to the organization and 30-day window; the error list is a separate bounded query. Each error response includes an X-Correlation-ID header and the same id in detail. Diagnostics contain no bearer value or provisioned profile attributes. Diagnostics older than 30 days are excluded from Operations and are deleted the next time a SCIM request is recorded or Operations is opened.
Refused authentication attempts are stored as audit windows per organization. The first attempt writes a row immediately. Further attempts during the fixed five-minute window update that row with the complete count, first and last occurrence times, and missing/refused categories after every request. An attempt after the window starts a new row.
The fixture suites follow published Okta and Entra ID request examples. They do not represent certification by either provider.
Errors
Section titled “Errors”Errors use schema urn:ietf:params:scim:api:messages:2.0:Error, a string status, a detail, a correlation id, and scimType where defined. Cuttlely uses invalidFilter, invalidValue, invalidSyntax, uniqueness, mutability, noTarget, and tooMany.
| Status | scimType |
Usual cause |
|---|---|---|
400 |
invalidFilter |
The filter uses an operator or attribute other than eq on the supported attributes, or it does not parse. |
400 |
invalidValue |
A value is not allowed, such as a userName that is not an email address, a repeated query parameter, or a group member that is not a current SCIM User id. |
400 |
invalidSyntax |
The body is not valid JSON, or a PATCH request has a malformed path, an unsupported operation, or no Operations list. |
400 |
mutability |
The request tries to change id, password, or roles, or to remove active. |
401 |
none | The bearer credential is missing, revoked, or past its rotation overlap. |
404 |
noTarget |
The User or Group id does not exist, or was deleted. |
409 |
uniqueness |
The account belongs to another organization, or the userName, externalId, or group name is already used. |
409 |
mutability |
The change would leave an organization without an active owner. |
413 |
tooMany |
The body is over 256 KB. |
415 |
invalidSyntax |
The Content-Type is not application/scim+json or application/json. |
429 |
tooMany |
The SCIM request limit was reached. The identity provider retries later. |
501 |
none | /Bulk is not supported. |
Each supported query parameter may appear only once. Repeated parameters, including filter, attributes, excludedAttributes, startIndex, and count, return 400 invalidValue in the SCIM error shape.