Skip to content
All articles
Protocols3 August 2026·8 min read

SCIM PATCH semantics, and why your handler is probably wrong

The specification defines three operations. Providers use them in ways the specification permits and your handler did not anticipate.

Paycux engineering

Most SCIM handlers are written against a PUT. A full resource arrives, you replace what you hold, everything is comprehensible. Then a provider sends a PATCH, and the request body is a list of operations describing a change rather than a state, and the comfortable model stops applying.

A PATCH carries an Operations array. Each entry has an op of add, remove or replace, an optional path selecting what to change, and usually a value. That is the whole grammar, and almost every difficulty comes from the optional word in the middle.

The path is a query language

A path can be a plain attribute name, a sub-attribute, or an attribute filtered by an expression. Removing one member from a group arrives as a path of members[value eq "9f2c"] with no value at all. If your parser handles dotted paths but not bracketed filters, you will silently ignore every single-member removal a provider sends, and the group will grow monotonically until somebody notices.

When the path is absent, the value is an object whose keys are attribute names and the operation applies to each of them. A deactivation frequently arrives that way: no path, value of {"active": false}. A handler that requires a path will treat it as malformed and return an error the provider retries forever.

  • Support bracketed filters on multi-valued attributes, not just dotted paths
  • Handle a path-less operation whose value is a map of attributes
  • Match attribute names case-insensitively — the specification says so and providers rely on it
  • Accept both {"active": false} and {"active": "False"}; several providers send the string
  • A remove with no value is normal; do not require one

If you handle dotted paths but not bracketed filters, every single-member removal is silently dropped.

Replace on a multi-valued attribute is ambiguous

This is the one that produces the memorable incidents. A replace on members can mean replace the entire membership with this list, or replace the matched members with these values, and providers do both. If you guess authoritative replacement and the provider meant an incremental change, you have just removed everyone else from the group.

Do not guess from the operation alone. Decide from the shape: a replace whose path selects specific members is a targeted change; a replace on the bare attribute with a complete list is authoritative. When a request is genuinely ambiguous, converge rather than obey — fetch the current membership from the provider and diff, which is the behaviour you want for reliability anyway.

Atomicity and the responses that matter

A PATCH is a single request and must apply as a unit. If the third of five operations is invalid, the first two must not persist, because the provider will retry the whole request and you will apply them twice. Wrap the operation loop in one transaction and validate every entry before executing any of them.

Get the response codes right too, because they drive retry behaviour on the other side. A successful PATCH returns the updated resource or an empty success. A reference to a resource you do not hold is a not-found, not a server error — a server error will be retried indefinitely, while a not-found tells the provider to reconcile. And a duplicate create should return a conflict with the existing resource identifier so the provider can adopt it rather than looping.

  • One transaction per request; validate all operations before applying any
  • Return the resource version so conditional requests work
  • Not-found for unknown targets, conflict for duplicates, server error only when it really is one
  • Log the raw operations array — reconstructing a bad sync without it is guesswork

Everything here, already built

Sign-in, enterprise SSO, directory provisioning, roles and an audit trail behind one API. Start with the quickstart and have a working sign-in this afternoon.

Start selling to enterprise customers

Create an account, point sign-in at Paycux, and get back to the part of the product that is actually yours.