Skip to content
English
  • There are no suggestions because the search field is empty.

Refapp SCIM API — supported surface

Companion to Setting up SCIM provisioning with Microsoft Entra ID and SCIM authentication.

The Refapp SCIM API is not a complete implementation of SCIM 2.0. It implements the subset that Microsoft Entra ID's provisioning service actually uses, and it is shaped by that decision throughout. If you are configuring Entra, you will not notice the gaps. If you are writing your own SCIM client, read this page first — the parts we do not implement are listed here rather than left to be discovered.

Everything on this page is advertised by the API itself. Rather than trusting this document, ask your own tenant:

GET <root>/scim/v2/<companyId>/ServiceProviderConfig GET <root>/scim/v2/<companyId>/ResourceTypes GET <root>/scim/v2/<companyId>/Schemas 

All three require the same bearer token as the rest of the API. If those documents ever disagree with this page, they are correct.

Supported capabilities

From ServiceProviderConfig:

Capability Supported Notes
patch Yes PATCH is how Entra sends nearly every update
filter Yes Heavily restricted — see Filtering. maxResults is 100
bulk No maxOperations and maxPayloadSize are both 0
sort No sortBy and sortOrder are accepted and silently ignored
etag No No If-Match / If-None-Match concurrency control
changePassword No Refapp accounts have no IdP-settable password

The only authentication scheme is oauthbearertoken (RFC 6750) — a static bearer token you generate in Refapp. There is no OAuth token endpoint and no client-credentials flow. See SCIM authentication.

Resources

Exactly two, from ResourceTypes:

Resource Endpoint Schema
User /Users urn:ietf:params:scim:schemas:core:2.0:User
Group /Groups urn:ietf:params:scim:schemas:core:2.0:Group

A Refapp Group is a subaccount — the resource description reads "Identity provider group mapped onto Refapp subaccounts". Groups are not permissions and not teams; they select which subaccounts a user can work in. Roles are carried on the User resource instead, under roles.

The enterprise user extension (urn:ietf:params:scim:schemas:extension:enterprise:2.0:User) is declared on Userwith required: false. We read exactly one attribute from it: costCenter. Nothing else in that extension is stored — manager, employeeNumber, department, division and organization are accepted and discarded.

There are no Refapp-proprietary schema extensions. Everything we consume is standard SCIM.

User attributes

Attribute Type Multi Mutability Required
userName string   readOnly, unique per server Yes
name.givenName string   readWrite  
name.familyName string   readWrite  
emails[].value / .type / .primary complex yes readWrite  
phoneNumbers[].value / .type complex yes readWrite  
preferredLanguage string   readWrite Yes
active boolean   readWrite Yes
roles[].value complex yes readWrite  
externalId string   readWrite (see below)

Three of these deserve emphasis, because each is a way to get a create rejected or a user into a state you did not intend:

  • userName is readOnly because it is derived, not stored. A Refapp user's identifier is their email address, and userName is simply that address read back. You never set it — not at creation and not later — and a write that targets userName is ignored rather than rejected.The address itself is mutable: set it through emails and userName follows it automatically on the next read. So "read-only" here means "this field is a view of the address, edit the address instead", not "the address is fixed".It is unique across the server, so moving a user onto an address another user already holds answers 409.userName echoes the address you filtered on. Where a company uses plus-addressing, the stored address carries a label (anna+acme@example.com) while a client filters on the plain one. A lookup that matched through that fallback answers userName with the address you asked for, so the client recognises the match, while emails carries the address actually stored. The two disagreeing is normal and is the only case where they differ — see the FAQ.
  • preferredLanguage is declared required. Entra's default customappsso mapping includes it, so do not delete that mapping when trimming the attribute list.
  • externalId is the identity link. It is not in the schema document because SCIM defines it as a common attribute present on every resource, but it is the single most important field you send. It must carry the same value your SAML assertion puts in the NameID. Getting this wrong is the failure described in step 1 of the setup guide.

roles carries only a value sub-attribute. The display, type and primary sub-attributes that appear in Entra's default target schema are not read.

Group attributes

Attribute Type Multi Mutability Required
displayName string   readWrite Yes
members[].value complex yes readWrite  

displayName is the join key: it is matched against the subaccount's configured SSO group names. See group mapping in the setup guide.

Filtering

This is the narrowest part of the API, and the most common source of surprise.

Two attributes are filterable, with one operator:

Supported
Attributes userName, externalId
Operators eq

That is the entire filter surface. It is exactly what Entra's provisioning service issues when matching an existing user, which is why it is enough.

Anything else is rejected explicitly rather than ignored — an unsupported filter is a 400, not an empty result:

GET /Users?filter=displayName eq "x"  {"schemas":["urn:ietf:params:scim:api:messages:2.0:Error"],  "status":"400","scimType":"invalidFilter",  "detail":"Filtering on displayName is not supported, only userName and externalId"} 
GET /Users?filter=userName sw "a"  {"schemas":["urn:ietf:params:scim:api:messages:2.0:Error"],  "status":"400","scimType":"invalidFilter",  "detail":"Only the eq operator is supported, not sw"} 

There are no compound filters: no and, no or, no not, no parentheses.

Sorting is the exception to this strictness. sortBy and sortOrder do not error — they return 200 with an unsorted list. ServiceProviderConfig declares sort.supported: false, so this is advertised, but it is the one place where an unsupported feature fails quietly instead of loudly. Do not rely on result ordering.

Paging

List responses are standard SCIM ListResponse documents:

{"schemas":["urn:ietf:params:scim:api:messages:2.0:ListResponse"],  "totalResults":0,"startIndex":1,"itemsPerPage":0,"Resources":[]} 

startIndex is 1-based. maxResults is 100, so a client that needs the full set must page with startIndex and count.

Errors

Errors are SCIM Error documents, not Refapp's normal HTML or JSON error pages:

Status When
400 Malformed request, unsupported filter, or a rejected value — including an email outside the company's allowed domains
401 Any authentication problem at all — see below
404 Resource id not found
409 Uniqueness conflict, typically a userName already in use

Every authentication failure returns the same 401, whatever the cause — a wrong token, a token belonging to a different company, an unknown company id, or a company that does not have SCIM enabled:

{"schemas":["urn:ietf:params:scim:api:messages:2.0:Error"],  "status":"401","detail":"Bearer token is missing or not valid"} 

This is deliberate. The endpoint cannot be used to work out which company ids exist or which of them have SCIM half-configured. The cost is that a 401 tells you nothing about which of those four things went wrong, so work through them in order: troubleshooting a 401.

Note that a URL that is not a SCIM route at all — a misspelled endpoint, say — falls through to the normal Refapp web application and returns an HTML page. If you get HTML back, check the path before anything else.