SCIM Patch Operations
- Overview
- Targeting Properties
- SCIM User Examples
- SCIM Group Examples
SCIM Patch Operations aggregate a collection of operations on a target resource. SCIM Patching is described in RFC7644 Section 3.5.2. This guide covers basics and common usecases.
Overview
An example SCIM User Patch Request might look like this:
PATCH {{PAPI_URL}}/scim/v2/Users/{{USER_ID}} HTTP/1.1
Authorization: Bearer {{PAPI_TOKEN}}
Content-Type: application/json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "replace",
"path": "title",
"value": "Bossman"
},
{
"op": "replace",
"path": "name:familyName",
"value": "Smith"
},
{
"op": "add",
"path": "emails",
"value": { "value": "my-new-emails", "primary": true }
},
{
"op": "remove",
"path": "emails[primary eq false]"
}
]
}
Each SCIM Patch consists of one or more Operations
in an ordered list.
Each operation has the following properties:
Property | Description | Values/Notes/... |
---|---|---|
op |
Patch Operation | add , replace , remove |
path |
SCIM Path to value we're operating on | Optional |
value |
Value being added or using as replacement | See note if no path |
The op
property defines the operation, each doing as it suggests. replace
completely replaces a value, remove
removes a value (and can operate on single- or multi-valued properties), and add
adds values to multi-valued properties (but also can act as a replace
).
The path
property targets the value being modified, e.g., the current userName
, a collection of addresses, or properties within collections. SCIM Filters may be used to further refine targeting. path
may be omitted; see below.
Not all PatchOps require the use of existing entity values, e.g., remove
operations don't care about the current value.
Targeting Properties
SCIM Paths use a colon :
as a path separator. This is true even when a path portion contains a colon, like the extension schemas. Example paths include:
const examplePaths = [
{ "path": "title" }, // Root-level property
{ "path": "name:familyName" }, // Nested property
{ "path": "urn:SocialChorus:1.0:User:managerName" }, // User extension property
]
Some properties are not at the root level, like SCIM's Enterprise User extension schema (urn:ietf:params:scim:schemas:extension:enterprise:2.0:User
, or Firstup's own User extension schema, urn:SocialChorus:1.0:User
, or SocialChorus:1.0:User
, depending on configuration.
SCIM paths use a colon :
as a path separator. To target our User extension schema's workLocation
property the path would be:
{
"op": "replace",
"path": "urn:SocialChorus:1.0:User:workLocation",
"value": "Updated work location"
}
path
-less Targeting
If the optional path
property is omitted it is assumed the target is the entire resource, e.g., a SCIM User or Group. In this case the value
property's own property names will align with the resource.
For example, we can replace a user's displayName
with either a path:
{
"op": "replace",
"path": "displayName",
"value": "User McUser"
}
Or by using a replacement value object:
{
"op": "replace",
"value": {
"displayName": "User McUser"
}
}
Notes
- A
path
-lessreplace
replaces the entire object. It is not a merge. remove
operations require apath
.- Removes are value-less operations.
Lists (Arrays)
Targeting a list element requires filtering. For example, if we wanted to updated a misspelled street name in a user's address we might target based on the misspelling:
{
"op": "replace",
"path": "address[streetAddress eq \"42 Marn St\"].streetAddress",
"value": "42 Main St"
}
SCIM does not support position-based operations, meaning we can't target the "first" address, or re-order addresses from within SCIM itself. Those types of operations would need to be handled on the client side followed by a value replacement.
Objects (like name
)
A SCIM User's name
property is an object with givenName
and familyName
, e.g.,
{
"name": {
"givenName": "Leonardo",
"familyName": "da Vinci"
}
}
Updating the familyName
follows SCIM pathing rules using a colon :
as the separator:
{
"op": "replace",
"path": "name:familyName",
"value": "Ninja Turtle"
}
A complete value replacement is similar, with a subtle "gotcha":
{
"op": "replace",
"value": {
"name": {
"familyName": "Ninja Turtle",
"givenName": "Leonardo"
}
}
}
Here we're replacing the name
property, but if we don't include givenName
its value will be removed. In other words the value object's name
property isn't merged with the resource's current value--it's replaced.
Lists of Objects (like emails
or addresses
)
Lists of objects boil down to what we've already seen, for example targeting an address as shown in the example above. The first step is to target the specific list item, the second is to target a specific property (or the entire object).
SCIM User Examples
Title
Replacing a value is the most common PATCH operation. Here we replace the user's title
attribute.
PATCH {{PAPI_URL}}/scim/v2/Users/{{USER_ID}} HTTP/1.1
Authorization: Bearer {{PAPI_TOKEN}}
Content-Type: application/json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "replace",
"path": "title",
"value": "Da Boss"
}
]
}
As noted, SCIM PatchOps are a collection of operations: a single PATCH request may contain multiple operations. Here we'll replace both the user's title
and locale
:
PATCH {{PAPI_URL}}/scim/v2/Users/{{USER_ID}} HTTP/1.1
Authorization: Bearer {{PAPI_TOKEN}}
Content-Type: application/json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "replace",
"path": "title",
"value": "Boss"
},
{
"op": "replace",
"path": "locale",
"value": "en-UK"
}
]
}
Emails
Adding an Email
Adding an email is straight-forward:
PATCH {{PAPI_URL}}/scim/v2/Users/{{USER_ID}} HTTP/1.1
Authorization: Bearer {{PAPI_TOKEN}}
Content-Type: application/json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "add",
"path": "emails",
"value": {
"value": "baz@amazon.com"
}
}
]
}
Note: value
shows up a lot in the above request. This is because the object used in the value
itself has a value
attribute: emails have value
and primary
attributes.
Adding multiple emails is also possible by providing an array of email objects as the PatchOp's value
attribute:
PATCH {{PAPI_URL}}/scim/v2/Users/{{USER_ID}} HTTP/1.1
Authorization: Bearer {{PAPI_TOKEN}}
Content-Type: application/json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "add",
"path": "emails",
"value": [
{
"value": "plugh@amazon.com"
},
{
"value": "xyzzy@amazon.com"
}
]
}
]
}
Multiple Primary Emails
Only one email at a time can be the primary email. If multiple emails are marked as primary in the request it is a validation error. Since there's a primary email by default, even if none are set explicitly, a PatchOp that adds an additional primary email will result in the previous primary email being "demoted" and the added email will become the new primary:
emails
prior to PatchOp add
:
"emails": [
{
"value": "plugh@com.com",
"primary": true
},
{
"value": "xyzzy@com.com",
"primary": false
}
],
If we add emails, one marked as primary:
PATCH {{PAPI_URL}}/scim/v2/Users/{{USER_ID}} HTTP/1.1
Authorization: Bearer {{PAPI_TOKEN}}
Content-Type: application/json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "add",
"path": "emails",
"value": [
{
"value": "foo@com.com",
"primary": true
},
{
"value": "bar@com.com",
"primary": false
}
]
}
]
}
This will result in the following emails
:
"emails": [
{
"value": "plugh@com.com",
"primary": false
},
{
"value": "xyzzy@com.com",
"primary": false
},
{
"value": "foo@com.com",
"primary": true
},
{
"value": "bar@com.com",
"primary": false
}
]
The request's intent is impossible to guess, so we best-effort based on what we think is meant.
Swapping the Primary Email
To change the primary email we can replace
the primary
value on the email we want to make primary. This has the secondary effect of setting the previously-primary email's primary
attribute to `false.
PATCH {{PAPI_URL}}/scim/v2/Users/{{USER_ID}} HTTP/1.1
Authorization: Bearer {{PAPI_TOKEN}}
Content-Type: application/json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "replace",
"path": "emails[value eq \"bar@com.com\"].primary",
"value": true
}
]
}
Phone Numbers
Phone numbers are another multi-valued property, each with a type
and value
property. The Firstup API currently supports two types of phone numbers, "main"
and "mobile"
. The phone number type must be sent with the phone number. Phone numbers with a missing or invalid type are silently ignored.
Removing a Phone Number
Phone numbers may be removed by their value or type, or, most robustly, with both. For example, let's say a user has the same phone number for both their "main"
and "mobile"
number. We cannot use the phone number as the value comparison because this would remove both numbers. We can specify just the type
, but we can also specify the number
just in case.
Photos
Firstup uses the photos
property to define the user avatar, and nothing else. Only the first photo in the photos
array is examined, validated, and used. All other photos
entries are ignored. It is, however, still a multi-valued property because SCIM says it is, so it must be treated as such in PatchOps.
Custom Attributes
Adding a Single Custom Attribute
PATCH {{PAPI_URL}}/scim/v2/Users/{{USER_ID}} HTTP/1.1
Authorization: Bearer {{PAPI_TOKEN}}
Content-Type: application/json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "add",
"path": "urn:SocialChorus:1.0:User:customAttributes",
"value": { "name": "ca1", "value": "ca1 value" }
}
]
}
Adding Multiple Custom Attributes
PATCH {{PAPI_URL}}/scim/v2/Users/{{USER_ID}} HTTP/1.1
Authorization: Bearer {{PAPI_TOKEN}}
Content-Type: application/json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "add",
"path": "urn:SocialChorus:1.0:User:customAttributes",
"value": [
{ "name": "ca1", "value": "ca1 value" },
{ "name": "ca2", "value": "ca2 value" },
{ "name": "ca3", "value": "ca3 value" }
]
}
]
}
Replacing a Custom Attribute Value
We specify the path to the custom attribute with a combination of the path, a query, and the attribute we're changing. Normally for a custom attribute this would be the value
attribute.
For example, if we're updating a custom attribute named "job_code"
we'd do it like this:
PATCH {{PAPI_URL}}/scim/v2/Users/{{USER_ID}} HTTP/1.1
Authorization: Bearer {{PAPI_TOKEN}}
Content-Type: application/json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "replace",
"path": "urn:SocialChorus:1.0:User:customAttributes[name eq \"job_code\"].value",
"value": "THX1138"
}
]
}
Removing a Custom Attribute
Similarly if we want to completely remove a custom attribute named "employee_type":
PATCH {{PAPI_URL}}/scim/v2/Users/{{USER_ID}} HTTP/1.1
Authorization: Bearer {{PAPI_TOKEN}}
Content-Type: application/json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "remove",
"path": "urn:SocialChorus:1.0:User:customAttributes[name eq \"employee_type\"]"
}
]
}
Removing All Custom Attributes
We can remove all custom attributes with remove
:
PATCH {{PAPI_URL}}/scim/v2/Users/{{USER_ID}} HTTP/1.1
Authorization: Bearer {{PAPI_TOKEN}}
Content-Type: application/json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "remove",
"path": "urn:SocialChorus:1.0:User:customAttributes"
}
]
}
Or we can replace the value with an empty array:
PATCH {{PAPI_URL}}/scim/v2/Users/{{USER_ID}} HTTP/1.1
Authorization: Bearer {{PAPI_TOKEN}}
Content-Type: application/json
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "replace",
"path": "urn:SocialChorus:1.0:User:customAttributes",
"value": []
}
]
}
SCIM Group Examples
Changing the members of a Group
The PATCH
endpoint allows to modify the list of members of a Group, adding or removing users:
"add"
: adds members to a Group."remove"
: deletes members from a Group."replace"
: replaces the whole list of members by the given one.
Please note that the "add"
and "replace"
operations do not have the same meaning.
In order to perform any of those operations, the identifierField
may be specified. It determines how we identify users. Allowed values are "id", "email" and "userName".
PATCH {{PAPI_URL}}/scim/v2/Groups/{{group_id}}
Authorization: Bearer {{token}}
Content-Type: application/scim+json
{
"schemas": [ "urn:ietf:params:scim:api:messages:2.0:PatchOp" ],
"SocialChorus:1.0:Group": { "identifierField":"email" },
"Operations": [
{
"op": "add",
"path": "members",
"value": [
{ "value":"jane.doe@my_company.com" },
{ "value":"john.smith@my_company.com" }
]
}
]
}
If 'identifierField'
is not present in the request, the users will be identified by 'userNameβ
.
PATCH {{PAPI_URL}}/scim/v2/Groups/{{group_id}}
Authorization: Bearer {{token}}
Content-Type: application/scim+json
{
"schemas": [ "urn:ietf:params:scim:api:messages:2.0:PatchOp" ],
"Operations": [
{
"op": "remove",
"path": "members",
"value": [
{"value":"username3"}
]
}
]
}
Rules when more than one operation is included for "members"
If a "replace"
operation is used, we recommend to not include any additional operations in the same request, in order to avoid unexpected results. The main reasons are:
- A
"replace"
is a complete replacement of the list of members. - Each
"members"
operation is performed asynchronously, so race conditions could apply.
If more that one operation is included in a PATCH call, we will apply the following rules:
- If the API request asks both to
"add"
and"remove"
with the same user, that user's changes will be ignored (it will be removed from those two operations). - If a
"replace"
operation is received, along with an"add"
and/or"remove"
, as the"replace"
means a full members replace, we will resolve it this way:- The users to `"remove"` will be ignored (as the previous existing users will be replaced)
- The users to `"add"` will be added to the "replace" list (kept in the final set of members)
Please note that if the three operations are used, and same user is both in the "add"
and the "remove"
lists, it will not be added to the "replace"
list (unless is was present there as well).
Modifying Group details
The general details of the Users' membership can be edited independently or at the same time with the list of members.
The Group-specific attributes that can be modified are (all String values):
Attribute name | Description | Example |
---|---|---|
displayName |
Name to be displayed | "Editors Group" |
description |
Description of this group purpose | "Users allowed to edit news releases" |
externalId |
Identifier used in an external system | "edit_group_123" |
For example, having a group with these details:
group_id: 1234
displayName: Editors Group
description: Users allowed to edit all news releases
externalId: edit_group_123
This API call would change the displayName
and description
:
PATCH {{PAPI_URL}}/scim/v2/Groups/1234
Authorization: Bearer {{token}}
Content-Type: application/scim+json
{
"schemas": [ "urn:ietf:params:scim:api:messages:2.0:PatchOp" ],
"Operations": [
{
"op": "replace",
"path": "displayName",
"value":"XYZ News Editors"
},
{
"op": "replace",
"path": "description",
"value":"News editors for the new project XYZ"
}
]
}
After performing the API call we'll have:
group_id: 1234
displayName: XYZ News Editors
description: News editors for the new project XYZ
externalId: edit_group_123