Skip to content

SCIM integration ​

A SCIM integration sends business identities from your provisioning system to Identity Stack. The provisioning system is usually an identity governance system or an HR system. The integration uses the standard SCIM 2.0 protocol (RFC 7643 and RFC 7644).

The provisioning system sends one person at a time, when a value changes. It does not send the complete set on a schedule, as the custom integration does.

A business identity can also name the Microsoft Entra ID user of the person. Identity Stack links the business identity to the user when the user exists.

Authentication ​

The integration authenticates with an access token from your own Microsoft Entra ID tenant. The integration uses the OAuth 2.0 client credentials flow. No user signs in.

Before you start, make sure that:

  • The Identity Stack enterprise application is installed in your tenant. See Setup Identity Stack.
  • You can register applications in Microsoft Entra ID.
  • You are an administrator in Identity Stack.

1. Register an application ​

  1. Go to the Microsoft Entra admin center.
  2. Go to App registrations and click New registration.
  3. Enter a name, for example Identity Stack SCIM.
  4. Select Accounts in this organizational directory only.
  5. Click Register.
  6. Write down the Application (client) ID and the Directory (tenant) ID.

2. Add a credential ​

Go to Certificates & secrets on the application.

  • Certificate (recommended). Upload the public key of a certificate. Keep the private key on the server that runs the integration.
  • Client secret. Click New client secret. Copy the value immediately. Microsoft Entra ID shows it only one time. Make a new secret before the old secret expires.

Keep the credential in a secure store, for example Azure Key Vault. Do not put it in source code.

3. API permissions ​

The application does not need an API permission or a role. The SCIM integration that you create in the next step gives the application its access.

Do not give the application the Admin role. The SCIM API does not use the role. The role gives full administrator access to the GraphQL API.

4. Create the SCIM integration in Identity Stack ​

Create one SCIM integration for each provisioning system. As an administrator, send this mutation to https://api.identitystack.dk/graphql:

graphql
mutation {
  createScimIntegration(
    input: {
      displayName: "Company HR"
      clientId: "00000000-0000-0000-0000-000000000001"
    }
  ) {
    scimIntegration {
      id
      enabled
      scimEndpoint
    }
  }
}
  • Use the Application (client) ID from step 1 as clientId.
  • A client ID can be in only one SCIM integration in your organization.
  • scimEndpoint is the base URL for your provisioning system: https://api.identitystack.dk/scim/v2.

A SCIM integration can read and change only the business identities that it created. To stop an integration, use the disableScimIntegration mutation. Identity Stack then refuses the requests of the integration. The business identities of the integration stay in Identity Stack.

5. Get an access token ​

Request a token from the Microsoft identity platform with the client credentials flow. Replace {tenant-id}, {client-id}, and {client-secret} with your values. URL-encode each value in the request body.

http
POST /{tenant-id}/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id={client-id}
&client_secret={client-secret}
&scope=https%3A%2F%2Fapi.identitystack.dk%2F.default

Use the token until it expires, then get a new token. Most provisioning systems do this for you. Configure OAuth 2.0 client credentials with these values:

SettingValue
Token URLhttps://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
Scopehttps://api.identitystack.dk/.default

6. Call the API ​

Send requests to https://api.identitystack.dk/scim/v2. Put the token in the Authorization header: Bearer {access-token}.

http
GET /scim/v2/ServiceProviderConfig HTTP/1.1
Host: api.identitystack.dk
Authorization: Bearer {access-token}

Send request bodies with the content type application/scim+json or application/json. If Identity Stack does not accept the token, see Errors.

The User resource ​

Each business identity is a SCIM User. A User has the core schema urn:ietf:params:scim:schemas:core:2.0:User. Each User must also have the extension urn:ietf:params:scim:schemas:extension:identitystack:2.0:User.

Core attributes ​

AttributeRequiredDescription
externalIdYesThe identifier of the person in your system. Unique in the integration. Case is important. Identity Stack uses it as the key of the person. Keep it the same for the life of the person.
userNameYesYour unique name for the person, for example a user name or an employee number. Unique in the integration. Case is not important. You can change it. Identity Stack does not use it to find the Microsoft Entra ID user.
name.givenNameYesThe first name. Maximum 128 characters.
name.familyNameYesThe last name. Maximum 128 characters.
activeNotrue if you do not send it. The value false makes the business identity inactive. See Deactivate or delete a person.

Identity Stack sets id and meta. meta.version is the version of the User. The ETag header also contains the version.

Identity Stack attributes ​

Put these attributes under the key urn:ietf:params:scim:schemas:extension:identitystack:2.0:User.

AttributeRequiredDescription
affiliationTypeYesemployment, enrollment, engagement, or other
birthDateNoYYYY-MM-DD
nationalityNoA two-letter country code, for example DK
personalEmailNoA private email address, not the work address. Maximum 200 characters.
personalPhoneNoA private phone number, not the work number, in E.164 format. For example +4512345678.
personalNumbersNoNational personal numbers: type (DkCpr) and value. One number for each type. See Personal numbers and identity documents.
identityDocumentReferencesNoPassports and national ID cards: type (Passport or IdentityCard), issuingCountry, and documentNumber. See Personal numbers and identity documents.
entraUserPrincipalNameNoThe User Principal Name (UPN) of the Microsoft Entra ID user of the person. See Links to Microsoft Entra ID users.
entraLinkStatusRead onlyThe status of the link to the user.
entraLinkSourceRead onlyasserted when the link comes from entraUserPrincipalName. manual when an administrator made the link. Empty when there is no link.
entraLinkedUserIdRead onlyThe object ID of the linked user. Empty when there is no link.

See Value formats for the format of each value.

Personal numbers and identity documents ​

These lists contain sensitive data. Identity Stack returns the stored values, as it does for the other attributes. An integration can read only the Users that it created.

You cannot use the lists in a filter. A filter is part of the URL, and many systems write URLs to their logs.

A PUT replaces the full lists. A PATCH can change one item with a path that has a filter. See Change a person.

Example ​

Request body:

json
{
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:User",
    "urn:ietf:params:scim:schemas:extension:identitystack:2.0:User"
  ],
  "externalId": "E-10042",
  "userName": "E-10042",
  "name": {"givenName": "Anne", "familyName": "Jensen"},
  "active": true,
  "urn:ietf:params:scim:schemas:extension:identitystack:2.0:User": {
    "affiliationType": "employment",
    "birthDate": "1990-01-01",
    "nationality": "DK",
    "personalEmail": "anne@example.net",
    "personalPhone": "+4512345678",
    "personalNumbers": [{"type": "DkCpr", "value": "010190-1234"}],
    "entraUserPrincipalName": "anne.jensen@contoso.dk"
  }
}

Response body:

json
{
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:User",
    "urn:ietf:params:scim:schemas:extension:identitystack:2.0:User"
  ],
  "id": "6f1c2a9e-3b4d-4e8f-9a1b-2c3d4e5f6a7b",
  "externalId": "E-10042",
  "userName": "E-10042",
  "name": {"givenName": "Anne", "familyName": "Jensen"},
  "active": true,
  "urn:ietf:params:scim:schemas:extension:identitystack:2.0:User": {
    "affiliationType": "employment",
    "birthDate": "1990-01-01",
    "nationality": "DK",
    "personalEmail": "anne@example.net",
    "personalPhone": "+4512345678",
    "personalNumbers": [{"type": "DkCpr", "value": "0101901234"}],
    "entraUserPrincipalName": "anne.jensen@contoso.dk",
    "entraLinkStatus": "linked",
    "entraLinkSource": "asserted",
    "entraLinkedUserId": "8a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
  },
  "meta": {
    "resourceType": "User",
    "created": "2026-10-20T08:15:00Z",
    "lastModified": "2026-10-20T08:15:00Z",
    "version": "W/\"1\"",
    "location": "https://api.identitystack.dk/scim/v2/Users/6f1c2a9e-3b4d-4e8f-9a1b-2c3d4e5f6a7b"
  }
}

Operations ​

All paths are relative to https://api.identitystack.dk/scim/v2. Each operation applies only to the Users of your integration.

Find a person ​

http
GET /Users?filter=externalId eq "E-10042"
  • A filter can use eq on id, userName, and externalId.
  • A filter can use gt or ge on meta.lastModified. Use it to find the Users that changed, for example a change of entraLinkStatus.
  • Other filters return 400 with the scimType invalidFilter.
  • A list contains a maximum of 100 Users on each page. Use startIndex and count to get the next page. The first index is 1. The order of the Users does not change between pages.
  • totalResults is the number of Users that match the filter. Use count=0 to get only this number.

Create a person ​

http
POST /Users

Send a User in the body, as in the example. Identity Stack returns 201 Created with the User and its id. The Location header contains the URL of the User.

Identity Stack returns 409 with the scimType uniqueness when a different User of the integration has the same userName or externalId.

Do these steps to create a person safely:

  1. Find the person with a filter on externalId.
  2. If you find no User, send the POST.
  3. If the POST returns 409, find the person again. A request that timed out can have created the User.

You can create a User with active set to false, for example for a person who starts later.

Change a person ​

  • PUT /Users/{id} replaces the User. Identity Stack clears the attributes that you do not send.
  • PATCH /Users/{id} changes only the attributes that you name. Use the SCIM operations add, replace, and remove.

Identity Stack returns 200 OK with the User.

A PATCH path can name a sub-attribute or an attribute of the extension. Put the extension key before the attribute name. For example:

  • name.familyName
  • urn:ietf:params:scim:schemas:extension:identitystack:2.0:User:affiliationType
  • urn:ietf:params:scim:schemas:extension:identitystack:2.0:User:personalNumbers[type eq "DkCpr"].value
json
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [
    {"op": "replace", "path": "name.familyName", "value": "Hansen"},
    {
      "op": "replace",
      "path": "urn:ietf:params:scim:schemas:extension:identitystack:2.0:User:personalPhone",
      "value": "+4587654321"
    }
  ]
}

Identity Stack ignores read-only attributes in a PUT. A PATCH that changes a read-only attribute returns 400 with the scimType mutability.

To prevent the loss of a newer change, send the last ETag in an If-Match header. If the User changed after that version, Identity Stack returns 412. The ETag is a weak entity tag, for example W/"3". Send the value exactly as you got it.

Deactivate or delete a person ​

Deactivate. Send a PUT or PATCH that sets active to false. Do this when the person leaves. Identity Stack keeps the business identity and its link to the Microsoft Entra ID user. The business identity is inactive.

  • To activate the business identity again, set active to true. For example, do this when the person comes back.
  • After 90 days as inactive, Identity Stack deletes the personal numbers and the document references of the business identity. The other attributes stay.

Delete. Send DELETE /Users/{id}. Identity Stack deletes the business identity, its personal numbers, its document references, and its link to the user. Identity Stack returns 204 No Content.

A deleted business identity cannot come back. You can create a new User with the same externalId and userName.

Reconcile ​

A provisioning system sends a change only one time. If a request fails, the two systems can become different. Compare your set of persons with Identity Stack regularly, for example every week:

  1. Get all Users with GET /Users, page by page.
  2. Create, change, deactivate, or delete Users until the two sets are the same.

Request formats ​

Identity Stack accepts these variants, because many provisioning systems send them:

  • Attribute names in all cases, for example UserName or username.
  • PATCH operation names in all cases, for example Replace.
  • A PATCH operation without a path, with an object as value. For example {"op": "replace", "value": {"active": false}}.
  • Boolean values as strings, for example "False".
  • Values of affiliationType in all cases, for example Employment.

Identity Stack ignores attributes of the core schema that are not in the tables above. It also ignores other schema extensions. Thus a User that you read can have fewer attributes than the User that you sent.

An attribute under the Identity Stack extension that is not in the table returns 400 with the scimType invalidSyntax. Usually the name has an error in it, for example affilationType.

Discovery ​

GET /ServiceProviderConfig, GET /Schemas, and GET /ResourceTypes show what the API supports.

  • /ServiceProviderConfig shows support for PATCH, filters, and entity tags.
  • /Schemas shows the rules of each attribute: required, mutability, returned, uniqueness, and case.
  • The API does not support bulk operations, sorting, password changes, or /Groups.

Errors ​

The SCIM API uses HTTP status codes. This is different from the GraphQL API. An error has a body with the SCIM error schema:

json
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
  "status": "400",
  "scimType": "invalidValue",
  "detail": "personalNumbers[type eq \"DkCpr\"].value is not a valid CPR number."
}

The detail names the attribute that is not correct. It never shows the value.

StatusscimTypeCause
400invalidValueA required attribute is missing, or a value has the wrong format.
400invalidSyntaxThe body is not valid JSON, or it has an attribute under the Identity Stack extension that is not in the table.
400invalidFilterThe filter is not supported.
400mutabilityA PATCH changes a read-only attribute.
401The token is missing or expired, or the token is not for the Identity Stack API.
403The token is valid, but no enabled SCIM integration in your organization has the client ID of the application. Identity Stack also returns 403 for a token of a user.
404Your integration has no User with this id. Identity Stack also returns 404 for the Users of other integrations.
409uniquenessA different User of the integration has the same userName or externalId.
412The User changed after the version in If-Match.
429You sent too many requests. Wait the number of seconds in the Retry-After header, then send the request again.

When a User has entraUserPrincipalName, Identity Stack links the business identity to the Microsoft Entra ID user with that UPN. Identity Stack does not use deleted users.

  • The link follows the user, not the UPN. When Identity Stack finds the user, it keeps the object ID of the user. If the UPN of the user changes in Microsoft Entra ID later, the link stays.
  • Identity Stack finds the user again only when the value changes. If you send a different entraUserPrincipalName, Identity Stack moves the link. If you remove entraUserPrincipalName, Identity Stack removes the link.
  • New users. If the user does not exist yet, the status is pending. Identity Stack looks for the user again in the background. Usually the link is made in less than 15 minutes after the user exists.

entraLinkStatus shows the result:

entraLinkStatusDescription
linkedThe business identity is linked to the user.
pendingNo user has the UPN yet, for example because you create the account later.
conflictThe user is already linked to a different business identity.
notAssertedThe User does not have entraUserPrincipalName, and there is no manual link.

An administrator can link a business identity manually in Identity Stack.

  • If the User does not have entraUserPrincipalName, Identity Stack keeps the manual link. entraLinkSource is manual, and entraLinkStatus is linked.
  • If the User has entraUserPrincipalName, the value that you send has priority. Identity Stack replaces the manual link and writes the change to the audit log.

A Microsoft Entra ID user can be linked to only one business identity. If the user is already linked to a different business identity, the existing link stays. This applies to business identities from all sources. The status is conflict until an administrator removes the existing link.

Value formats ​

  • CPR number. Use 10 digits, with or without a dash, for example 0101901234 or 010190-1234. The first six digits must be a valid date (DDMMYY). Identity Stack does not do a modulus 11 check. Many CPR numbers from 2007 and later do not pass the check. Identity Stack returns the number without the dash.
  • Birth date. Use the format YYYY-MM-DD. Identity Stack does not compare the birth date with the CPR number.
  • Phone number. Use the E.164 format: a +, the country code, and the number, with no spaces. The maximum is 15 digits. For example +4512345678.
  • Country codes. Use the two-letter code from ISO 3166-1 alpha-2, for example DK. This applies to the nationality and to the issuing country of a document.