SCIM Users API
Create, retrieve, list, update, and deactivate Users in SpeakUp through the SCIM 2.0 protocol. Designed for identity providers such as Microsoft Entra ID and Okta to automate user lifecycle management.
Users cannot be deleted through SCIM. Offboarding is done by deactivation.
{API_BASE_URL}/scim/v2/Users
On this page: Authentication · Supported features · Required fields · Behavioural constraints · Resolution priorities · User attributes · Create a User · Retrieve a User · List Users · Update with PUT · Update with PATCH · Status codes · Attribute mapping
Authentication
All SCIM requests need a bearer token. See what is SCIM provisioning and when should I use it? for how the connection is set up.
Authorization: Bearer <ACCESS_TOKEN>
Content-Type: application/scim+json
Accept: application/scim+json
Provisioning requires Full access for Users and Groups on the App Integration. Read only access can retrieve Users but cannot create, update, or deactivate them.
Supported features
Review these before configuring provisioning in your identity provider. Understanding the constraints up front prevents the most common provisioning failures.
| Feature | Supported | Notes |
|---|---|---|
| Create Users | Yes | Via POST /scim/v2/Users |
| Update User attributes | Yes | Via PUT or PATCH |
| Deactivate Users | Yes | Set active: false via PATCH |
| Reactivate Users | Yes | Set active: true via PATCH |
| Delete Users | No | SpeakUp does not support User deletion via SCIM. Use deactivation instead. |
| Sync group membership | Yes | Via the Groups API |
| Filter Users by attribute | Yes | userName, displayName, active |
| Paginated listing | Yes | startIndex and count parameters |
| Multiple emails per User | No | SpeakUp stores one email per User |
| Multiple phone numbers per User | No | SpeakUp stores one phone number per User |
Required fields
Three attributes are mandatory for every User creation request. Your identity provider must be configured to send all three.
| Attribute | Type | Constraint |
|---|---|---|
userName |
String | Must be a valid email address. Used as the unique identifier. |
displayName |
String | The name shown in the SpeakUp interface. |
active |
Boolean | Account state at creation. Typically true. |
The userName value must be a valid email address. It serves as the authoritative email for the account. If an emails array is also present, SpeakUp uses userName as the stored email and ignores conflicting values in the array.
Behavioural constraints
One identity per User. SpeakUp stores a single email address and a single phone number per account. If your identity provider sends arrays with multiple entries, the priority rules below determine which value is stored.
userName is the unique key. Creating a User with a userName that already exists returns 409 Conflict.
User deletion is not supported. A DELETE request is accepted but the User is deactivated rather than removed. For all offboarding, set active: false via PATCH. This preserves the account for audit and historical purposes and allows reactivation.
externalId comes from your identity provider. SpeakUp stores it as received and uses it to correlate accounts with your provider. It is not an internal lookup key. SpeakUp identifies Users by its own system-generated id, returned at creation.
Resolution priorities
SpeakUp stores one value where SCIM allows several. These rules decide which one.
Name
displayName, highest priority. Always send this.name.formattedname.givenNameandname.familyName, concatenated
Most identity providers send displayName by default. If your attribute mapping omits it, make sure name.formatted or the given and family names are present, or the User may be created with a blank display name.
userName, highest priorityemails[].valuewhereprimary: trueemails[0].value
Because userName takes precedence, map the User's primary email to userName. A different address in the emails array does not override it.
Phone number
phoneNumbers[].valuewhereprimary: truephoneNumbers[0].value
If no phone number is included, SpeakUp returns an empty array.
User attributes
Core attributes
| Attribute | Type | Description |
|---|---|---|
id |
String | System-generated unique identifier. Read only. Returned on creation. |
userName |
String | Unique email address, used as the primary identifier. Required. |
displayName |
String | Name shown in the SpeakUp interface. Required. |
active |
Boolean | Whether the account is active. Required. |
name.formatted |
String | Full name as a single string. |
name.givenName |
String | First name. |
name.familyName |
String | Last name. |
emails[].value |
String | Email address. Matches userName. |
emails[].type |
String | Always "work" in responses. |
emails[].primary |
Boolean | Always true in responses. |
phoneNumbers[].value |
String | Phone number. |
phoneNumbers[].type |
String | Always "work" in responses. |
phoneNumbers[].primary |
Boolean | Always true in responses. |
title |
String | Job title. |
userType |
String | User category, for example Employee or Contractor. |
externalId |
String | External identifier from your HR system or identity provider. |
preferredLanguage |
String | BCP 47 language tag. Defaults to "en-GB". |
groups[].value |
String | Group ID. Returned in responses. |
groups[].display |
String | Group name. Returned in responses. |
groups[].$ref |
String | Full URL to the Group resource. Returned in responses. |
meta.resourceType |
String | Always "User". |
meta.created |
String | ISO 8601 timestamp of creation. |
meta.lastModified |
String | ISO 8601 timestamp of last update. |
meta.location |
String | Canonical URL of this User resource. |
Enterprise extension attributes
To provision organisational attributes such as department or manager, include the enterprise extension schema urn:ietf:params:scim:schemas:extension:enterprise:2.0:User in the schemas array.
| Attribute | SpeakUp field | Type | Description |
|---|---|---|---|
employeeNumber |
employeeNumber |
String | Employee ID from your HR system. Maximum 128 characters. |
costCenter |
costCentreId |
Option list | Cost centre for billing allocation. |
department |
departmentId |
Option list | Department name. |
division |
divisionId |
Option list | Division name. |
organization |
organizationId |
Option list | Organisation or company name. |
manager |
managerId |
Complex | Reference to another User: value is the user ID, $ref the User URL, displayName the manager's name. |
Option list fields (department, division, costCenter, organization) are matched against existing values configured in your SpeakUp instance. If a value is not found, SpeakUp creates a new option list item using the provided value.
Create a User
POST /scim/v2/Users
Minimal request:
curl -X POST '{API_BASE_URL}/scim/v2/Users' \
-H 'Content-Type: application/scim+json' \
-H 'Accept: application/scim+json' \
-H 'Authorization: Bearer <ACCESS_TOKEN>' \
--data-raw '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "john.doe@example.com",
"displayName": "John Doe",
"active": true
}'
With the enterprise extension:
curl -X POST '{API_BASE_URL}/scim/v2/Users' \
-H 'Content-Type: application/scim+json' \
-H 'Accept: application/scim+json' \
-H 'Authorization: Bearer <ACCESS_TOKEN>' \
--data-raw '{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"userName": "jane.doe@company.com",
"displayName": "Jane Doe",
"active": true,
"name": {
"givenName": "Jane",
"familyName": "Doe"
},
"title": "Product Manager",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"employeeNumber": "EMP-99999",
"costCenter": "Product",
"department": "Product Management",
"division": "Product Division"
}
}'
Response, 201 Created
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "101",
"userName": "john.doe@example.com",
"displayName": "John Doe",
"active": true,
"name": {
"givenName": "John",
"familyName": "Doe",
"formatted": "John Doe"
},
"emails": [
{ "value": "john.doe@example.com", "type": "work", "primary": true }
],
"title": "Senior Software Engineer",
"phoneNumbers": [
{ "value": "+1-555-0123", "type": "work", "primary": true }
],
"preferredLanguage": "en",
"meta": {
"resourceType": "User",
"created": "2026-02-23T10:30:00Z",
"lastModified": "2026-02-23T10:30:00Z",
"location": "{API_BASE_URL}/scim/v2/Users/101"
}
}
Errors
400 Bad Request, missing or invalid required fields:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"scimType": "invalidValue",
"detail": "Validation failed: userName is required",
"status": "400"
}
409 Conflict, a User with the same userName already exists:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"scimType": "uniqueness",
"detail": "A user with userName 'john.doe@example.com' already exists",
"status": "409"
}
Retrieve a User
GET /scim/v2/Users/{id}
| Query parameter | Type | Description |
|---|---|---|
excludedAttributes |
String | Comma-separated list of attributes to omit from the response, for example meta. |
curl -X GET '{API_BASE_URL}/scim/v2/Users/101' \
-H 'Accept: application/scim+json' \
-H 'Authorization: Bearer <ACCESS_TOKEN>'
The response includes the User's groups array, showing every Group they belong to.
A User ID that does not exist returns 404:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"detail": "User not found",
"status": "404"
}
List Users
GET /scim/v2/Users
| Query parameter | Type | Default | Description |
|---|---|---|---|
startIndex |
Integer | 1 |
1-based starting position for pagination. |
count |
Integer | 50 |
Number of results per page. |
filter |
String | SCIM filter expression, URL-encoded. | |
excludedAttributes |
String | Comma-separated attributes to omit. |
Supported filter expressions
| Expression | Behaviour |
|---|---|
userName eq "john.doe@example.com" |
Exact match on userName |
userName sw "john" |
userName starts with the value |
displayName co "Doe" |
displayName contains the value |
displayName eq "John Doe" |
Exact match on displayName |
active eq true |
Active Users only |
curl -X GET '{API_BASE_URL}/scim/v2/Users?startIndex=1&count=10' \
-H 'Accept: application/scim+json' \
-H 'Authorization: Bearer <ACCESS_TOKEN>'
curl -X GET '{API_BASE_URL}/scim/v2/Users?filter=userName%20eq%20%22john.doe@example.com%22' \
-H 'Accept: application/scim+json' \
-H 'Authorization: Bearer <ACCESS_TOKEN>'
Update with PUT
PUT /scim/v2/Users/{id}
Fully replaces a User's attributes. Attributes omitted from the request body may be cleared, per SCIM semantics. Use PATCH if you only want to change specific fields.
The name, email, and phone resolution priorities above apply. userName always takes precedence as the stored email, and displayName for the stored name.
curl -X PUT '{API_BASE_URL}/scim/v2/Users/101' \
-H 'Content-Type: application/scim+json' \
-H 'Accept: application/scim+json' \
-H 'Authorization: Bearer <ACCESS_TOKEN>' \
--data-raw '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "john.doe@example.com",
"displayName": "John Michael Doe",
"active": true,
"name": {
"givenName": "John",
"familyName": "Doe"
},
"title": "Principal Software Engineer"
}'
Returns the full updated User with an updated meta.lastModified.
| Code | Cause |
|---|---|
| 400 | Invalid or missing required fields |
| 404 | User does not exist |
| 409 | userName already in use by a different User |
Update with PATCH
PATCH /scim/v2/Users/{id}
Updates one or more attributes without affecting others. This is the recommended method for lifecycle operations such as deactivation, reactivation, and attribute updates.
Update a single attribute:
curl -X PATCH '{API_BASE_URL}/scim/v2/Users/101' \
-H 'Content-Type: application/scim+json' \
-H 'Accept: application/scim+json' \
-H 'Authorization: Bearer <ACCESS_TOKEN>' \
--data-raw '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "replace", "path": "title", "value": "Engineering Manager" }
]
}'
Deactivate a User. This is how offboarding is done:
curl -X PATCH '{API_BASE_URL}/scim/v2/Users/101' \
-H 'Content-Type: application/scim+json' \
-H 'Accept: application/scim+json' \
-H 'Authorization: Bearer <ACCESS_TOKEN>' \
--data-raw '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "replace", "path": "active", "value": false }
]
}'
Set active to true to reactivate.
Returns the full User with updated attributes and meta.lastModified.
Status codes
| Code | Meaning | When it occurs |
|---|---|---|
| 200 | OK | Successful GET, PATCH, or PUT |
| 201 | Created | Successful POST |
| 400 | Bad Request | Missing required fields, invalid data format, or malformed patch operation |
| 401 | Unauthorized | Invalid or missing access token |
| 403 | Forbidden | Insufficient permissions for the operation |
| 404 | Not Found | User ID does not exist |
| 409 | Conflict | Duplicate userName or constraint violation |
| 500 | Internal Server Error | Server-side error. Contact SpeakUp support. |
Attribute mapping
How SCIM attributes map to SpeakUp fields. Use this when configuring attribute mapping in your identity provider.
Core schema
| SCIM attribute | SpeakUp field | Notes |
|---|---|---|
id |
id |
Read only. Returned by the API. |
userName |
email |
Required. Must be a valid email. Highest priority source for the stored email. |
displayName |
name |
Required. Shown in the interface. Highest priority source for the stored name. |
name.formatted |
name |
Used if displayName is absent. |
name.givenName |
firstName |
Used if neither displayName nor name.formatted is present. |
name.familyName |
lastName |
Concatenated with name.givenName as the last fallback. |
active |
enabled |
Required. |
emails[].value |
email |
Derived from userName. The array is accepted but userName takes precedence. |
title |
jobTitle |
|
phoneNumbers[].value |
phone |
See phone resolution priority above. |
preferredLanguage |
preferredLanguage |
BCP 47 language tag. Defaults to "en-GB". |
externalId |
externalId |
Stored as received. Not used as an internal lookup key. |
groups |
groups |
Read only in User responses. Managed through the Groups API. |
meta.created |
createdAt |
Read only. |
meta.lastModified |
updatedAt |
Read only. |
Enterprise extension
| SCIM attribute | SpeakUp field | Notes |
|---|---|---|
employeeNumber |
employeeNumber |
Maximum 128 characters. |
costCenter |
costCentreId |
Matched against existing values. Creates a new option list item if not found. |
department |
departmentId |
Matched against existing values. Creates a new option list item if not found. |
division |
divisionId |
Matched against existing values. Creates a new option list item if not found. |
organization |
organizationId |
Matched against existing values. Creates a new option list item if not found. |
manager |
managerId |
value is the user ID, $ref the User URL, displayName the manager's name. |