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 when | Use the Permit API or an SDK when |
|---|---|
| Your IdP holds the directory and you want Permit to follow it | Your own application creates users, for example at the authentication handoff point |
| You want provisioning and deprovisioning without writing sync code | You need user attributes or resource instances that SCIM doesn't carry |
| Group membership in the IdP should decide Permit role assignments | Roles come from your application's own logic |
How SCIM works
SCIM sends JSON over a REST API between two parties:
| Party | Role | Example |
|---|---|---|
| Identity provider (IdP) | Holds the directory of users and groups, and sends changes | Okta, Microsoft Entra ID |
| Service provider (SP) | Receives users and groups from the IdP | Permit |
When you create or update a user in the IdP, the IdP sends the change to the SP's SCIM endpoint.
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 shape | Pattern | Role assignments land in |
|---|---|---|
| Single-tenant, the original shape | https://scim.permit.io/scim/v2/{permit_project_id}/{permit_env_id} | The default tenant |
| Tenant-aware | https://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:
| Placeholder | Value |
|---|---|
{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 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.
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
defaulttenant, 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:userNamestays 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 thatdisplayName, they share the same role definition, and the secondPOST /Groupsreuses 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 theEngineeringrole in tenantacme-corpand not in tenantglobex. 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.
displayName | Role key | Result |
|---|---|---|
Engineering | Engineering | Creates or reuses the Engineering role |
Eng Team | EngTeam | Creates or reuses the EngTeam role |
EngTeam, sent after Eng Team | EngTeam | Rejected, because the existing role was created from a different displayName |