Skip to main content

SCIM provisioning with Permit

Understand what a System for Cross-domain Identity Management (SCIM) connection provisions into Permit.io, and which SCIM base URL to configure. This page is for the administrator who sets up that connection in an identity provider (IdP) such as Okta or Microsoft Entra ID. For the click-by-click setup, see SCIM integration with Okta or SCIM integration with Entra ID.

What SCIM is

SCIM is a standard protocol for creating, updating, and removing user identities across systems. Permit implements SCIM 2.0: RFC 7643 defines the core schema and RFC 7644 defines the protocol. For more information, see the SCIM website.

What a SCIM connection provisions into Permit

With a SCIM connection, your IdP provisions, updates, and deprovisions users in your Permit environment when the users change in the IdP. You don't write code to sync users from your IdP, and user records in Permit follow the IdP as the source of truth.

SCIM Groups become Permit roles, and group membership becomes role assignments. See How users, groups, and tenants relate.

When to choose SCIM over the Permit API

Use SCIM whenUse the Permit API or an SDK when
Your IdP holds the directory and you want Permit to follow itYour own application creates users, for example at the authentication handoff point
You want provisioning and deprovisioning without writing sync codeYou need user attributes or resource instances that SCIM doesn't carry
Group membership in the IdP should decide Permit role assignmentsRoles come from your application's own logic

How SCIM works

SCIM sends JSON over a REST API between two parties:

PartyRoleExample
Identity provider (IdP)Holds the directory of users and groups, and sends changesOkta, Microsoft Entra ID
Service provider (SP)Receives users and groups from the IdPPermit

When you create or update a user in the IdP, the IdP sends the change to the SP's SCIM endpoint.

Fun fact

SCIM 1.0 shipped in December 2011. The IETF standardized SCIM 2.0 in September 2015, in RFC 7643 and RFC 7644.

Permit SCIM base URLs

One Permit environment can serve several isolated customer tenants through a tenant-aware SCIM URL. The Permit SCIM server accepts two base URL shapes. Your SCIM client (Okta, Entra ID) appends the resource path (/Users, /Groups) to the base URL you configure:

Base URL shapePatternRole assignments land in
Single-tenant, the original shapehttps://scim.permit.io/scim/v2/{permit_project_id}/{permit_env_id}The default tenant
Tenant-awarehttps://scim.permit.io/scim/v2/{permit_project_id}/{permit_env_id}/v2/{tenant_key}The tenant whose key is in {tenant_key}

Both shapes expose the .../Users and .../Groups resource endpoints. In the tenant-aware URL, the {tenant_key} segment is required; there is no tenant-less /v2/Users. SCIM clients store the tenant in their endpoint configuration, so each tenant points its IdP at its own base URL.

Replace the placeholders as follows:

PlaceholderValue
{permit_project_id}Your Permit project ID or key. See Get the project ID.
{permit_env_id}Your Permit environment ID or key. See Get the environment ID.
{tenant_key}The key of the target Permit tenant. The SCIM server accepts [A-Za-z0-9_-]{1,64}: 1 to 64 alphanumerics, underscores, or hyphens. Another value returns HTTP 404 at the routing layer, before any handler runs.

EU users replace the scim.permit.io host with scim.eu-central-1.permit.io in either shape, for example https://scim.eu-central-1.permit.io/scim/v2/{permit_project_id}/{permit_env_id}/v2/{tenant_key}.

The second /v2/ segment is a routing prefix

The second /v2/ segment in the tenant-aware URL selects the multi-tenant SCIM API. It is not a SCIM protocol version. The SCIM 2.0 protocol version is the earlier /scim/v2/ segment.

Create the tenant in Permit first

The Permit SCIM server validates the format of {tenant_key} but doesn't create the tenant. It creates missing users, but not tenants. If a SCIM client points at a tenant key that doesn't exist in the environment, the role-assignment call fails and your IdP shows an error. Create the tenant before you provision the SCIM connection. See Create a tenant.

Choose between the two base URLs

  • Use the single-tenant URL when all provisioned users share one logical tenant, or when a SCIM connection already runs against it. Role assignments land in the default tenant, and the URL takes no tenant segment.
  • Use the tenant-aware URL when one Permit environment serves multiple customers and each needs its own isolated set of role assignments. Create one SCIM connection per tenant in your IdP, each pointing at its own /v2/{tenant_key}/... base URL.

Test a base URL with a sample request

Your IdP sends these requests; run one by hand to confirm a base URL and an API key before you connect the IdP. The Authorization header takes your Permit environment API key as a bearer token. Replace the placeholders, including the tenant key acme-corp:

curl -i -X POST \
"https://scim.permit.io/scim/v2/{permit_project_id}/{permit_env_id}/v2/acme-corp/Users" \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/scim+json" \
-d '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "john@permit.io",
"name": { "givenName": "John", "familyName": "Doe" },
"emails": [{ "value": "john@permit.io", "primary": true }],
"active": true
}'

A created user returns HTTP 201 with the SCIM User resource. The id field holds the ID that Permit assigned, and userName becomes the Permit user key:

{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "john@permit.io",
"active": true,
"id": "7f9c3e1a-0b2d-4c5e-8a1f-2d3b4c5e6f70",
"emails": [{ "value": "john@permit.io" }],
"name": { "givenName": "John", "familyName": "Doe" }
}

Open Directory in the Permit dashboard to confirm the user appears with john@permit.io as its key.

Repeat the same request and the server returns HTTP 409, because userName is already taken:

{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"detail": "User already exists.",
"status": 409
}

A request without a Permit environment API key in the Authorization header returns HTTP 401 with the same error schema and "detail": "Unauthorized".

How users, groups, and tenants relate

  • Users are environment-scoped. A SCIM User created through any tenant URL is provisioned once in the Permit environment. The URL accepts the {tenant_key} segment, but it doesn't partition users: userName stays unique across the environment.
  • Groups (roles) are environment-scoped definitions. A Group with displayName: "Engineering" resolves to a single role in the environment. If two tenants both create a Group with that displayName, they share the same role definition, and the second POST /Groups reuses the existing role instead of failing.
  • Group membership (role assignment) is tenant-scoped. Adding a user to a Group through /v2/{tenant_key}/Groups/... grants that user the role in that tenant only. The same user can hold the Engineering role in tenant acme-corp and not in tenant globex. Membership in one tenant has no effect on membership in another.

Role definitions aren't duplicated per tenant, and each tenant's user-to-role bindings stay independent. This follows the Permit data model: roles are defined per environment, and assignments carry a tenant.

How a Group displayName becomes a role key

The Permit SCIM server derives the role key from the Group's displayName by removing every character outside A-Z, a-z, 0-9, -, and _, then matches on that key rather than on the raw displayName. Two different display names that normalize to the same key, for example Eng Team and EngTeam, which both become EngTeam, are rejected instead of merged.

displayNameRole keyResult
EngineeringEngineeringCreates or reuses the Engineering role
Eng TeamEngTeamCreates or reuses the EngTeam role
EngTeam, sent after Eng TeamEngTeamRejected, because the existing role was created from a different displayName

Next steps