<@U08614ZV82F> Do resource instance keys have to b...
# permit-saas
a
@Ask Inkeep Do resource instance keys have to be unique across tenants within an environment? If so, what's the rationale here?
p
Hey, thanks for reaching out ! It seems our team is a little busy right now, a human will be with you within the next hour or so. In the meantime, can I help you ?
a
Thanks for tagging me @Aden McCusker. I'll look through relevant sources and get back to you shortly.
Yes, resource instance keys must be unique within their namespace, which for resource instances is the Environment. (1) The key needs to be unique in its namespace, and for most objects (including resource instances) , "namespace" means Environment. Specifically, uniqueness is based on the
(object_type, key)
tuple within that namespace. ​*Rationale:*​ The key serves as a unique identifier that Permit uses to identify the resource instance for permission checks—you pass this identifier to the
permit.check()
API. (2) A key can be anything (a database ID, URL slug, UUID, etc.) as long as it's unique on your end and URL-friendly. This means you ​*can*​ have: • Two resource instances with the same key in ​*different*​ Environments • A Resource keyed "john" and a Role keyed "john" in the same Environment (different object types) But you ​*cannot*​ have: • Two resource instances of the same resource type with the same key in the same Environment (1) The design allows you to use your own system's identifiers as keys while ensuring Permit can unambiguously resolve which resource instance you're referring to during permission checks.
a
@Ask Inkeep Mmm, so in the common case of auto incrementing pks in a multi-tenanted app, is the suggested solution to prefix the resource instance key with an ID unique to the tenant?
a
Thanks for tagging me @Aden McCusker. I'll look through relevant sources and get back to you shortly.
Yes, prefixing the resource instance key with a tenant identifier is a common and practical approach. The documentation shows that when creating resource instances, you provide a
key
that uniquely identifies that instance within the environment. (1) For example, if you have a
repository
resource with auto-incrementing IDs, you could structure your keys like:
Copy code
await permit.api.resource_instances.create({
  key: "react",
  resource: "repository",
  tenant: "default",
  attributes: {
    private: "false",
    owner: "Company C",
  },
}) ;
In your multi-tenant scenario with auto-incrementing PKs, you'd adapt this pattern to something like
{tenant_key}:{pk}
or
{tenant_key}-{pk}
(e.g.,
acme-corp:123
or
stripeinc-456
) . This ensures uniqueness across tenants while still allowing you to derive the key programmatically from your existing data. The key just needs to be URL-friendly and unique within the environment—your own system's identifiers work fine as long as you make them unique across tenants.
To learn more, see the sources I considered: [[1] Sync Data into your App](https://docs.permit.io/overview/sync-applications-data)
a
@Ask Inkeep And how does this work with users? From my testing in the UI, it looks like an attempt to create a user in tenant x while that user already exists in tenant y, it updates that user's tenant from x to y. Is this logic in the permit frontend, or at the API level?
a
Thanks for tagging me @Aden McCusker. I'll look through relevant sources and get back to you shortly.
Users in Permit work differently from resource instances—a single user can belong to ​*multiple tenants simultaneously*​. (1) , (2) Users are assigned to tenants via roles, not by being "created in" a tenant. (1) When you assign a user a role, you specify both the role and the tenant:
Copy code
await permit.api.assignRole({
  role: "AccountOwner",
  tenant: cleanedEmail,
  user: email,
}) ;
(3) So the behavior you're seeing isn't replacing tenant X with tenant Y—it's ​*adding*​ the user to tenant Y while they remain in tenant X. Each user can have different roles in different tenants. (2) From the documentation: "Users are assigned to tenants (each user can belong to more than one tenant) and can then be assigned roles and permissions." (4) The same user can exist in multiple tenants with different roles—for example, an Admin in one tenant and a Manager in another. (5) This is handled at the API level, not just the frontend. To explicitly add a user to a new tenant with a role:
Copy code
const user = await permit.tenants.assignUser({
  key: "user|123456789",
  email: "<mailto:john@smith.com|john@smith.com>",
  first_name: "John",
  last_name: "Smith",
  role_assignments: [
    {
      role: "Manager",
      tenant: "Marketing",
    },
  ],
}) ;
This adds the user to the "Marketing" tenant without removing them from any existing tenants.
To learn more, see the sources I considered: [[1] Multi-Tenant Authorization](https://docs.permit.io/concepts/multi-tenant-authorization) [[2] Policy Basics](https://docs.permit.io/how-to/build-policies/policy-basics) [[3] Fintech / Banking Demo Application](https://docs.permit.io/modeling/mesa-verde) [[4] Sync Users](https://docs.permit.io/how-to/sync-users) [[5] Sync your First User](https://docs.permit.io/overview/sync-your-first-user-with-sdk)
a
@Ask Inkeep I see, so will a create user API call with a user key that already exists in a different tenant act the same way as I observed in the frontend?
a
Thanks for tagging me @Aden McCusker. I'll look through relevant sources and get back to you shortly.
Yes, the API behaves the same way. The
syncUser
function (and the underlying API) is designed to create the user if they don't exist, or update them if they do. (1) When you call the create/sync user endpoint with a user key that already exists, it won't fail or replace the user—it will update that user's information and add any new role assignments you specify. (2) , (3) For example, if you sync a user with role assignments to a new tenant:
Copy code
const user = await permit.tenants.assignUser({
  key: "user|123456789",
  email: "<mailto:john@smith.com|john@smith.com>",
  first_name: "John",
  last_name: "Smith",
  role_assignments: [
    {
      role: "Manager",
      tenant: "Marketing",
    },
  ],
}) ;
(2) This will add the user to the "Marketing" tenant with the "Manager" role while preserving their existing tenant memberships. The key point is that users are assigned to tenants via roles, and a single user can belong to multiple tenants simultaneously. (4) So the API call doesn't "move" the user—it extends their tenant memberships. If you want to assign a role to an existing user in a new tenant without re-syncing their profile data, you can use the role assignment endpoint directly:
Copy code
const result = await permit.roles.assign(userId, roleData.role, roleData.tenant) ;
(2)
To learn more, see the sources I considered: [[1] Sync Users](https://docs.permit.io/how-to/sync-users) [[2] Sync your First User](https://docs.permit.io/overview/sync-your-first-user-with-sdk) [[3] Sync your Application Data | Permit.io Documentation](https://docs.permit.io/overview/sync-application-data-into-permit) [[4] Multi-Tenant Authorization](https://docs.permit.io/concepts/multi-tenant-authorization)
a
@Ask Inkeep Can I update a user to add an extra registered tenant to them? Does this necessarily require also providing them a role? AFAIK, roles are not per tenant, so I assume I don't need a role here, but I'm not sure what API call adds a tenant to a user without also adding a role. I seem to be able to do it in the permit frontend though.
p
Hey, thanks for reaching out ! It seems our team is a little busy right now, a human will be with you within the next hour or so. In the meantime, can I help you ?
a
Thanks for tagging me @Aden McCusker. I'll look through relevant sources and get back to you shortly.
Based on the available documentation, users are assigned to tenants ​_via roles_​ in Permit. (1) The documentation states: "Tenants represent a single instance of the application's environment... Permissions and policies can be defined at the tenant level." And from the sync documentation: when adding a user to a tenant, you provide
role_assignments
that include both the
role
and
tenant
. (2)
Copy code
const user = await permit.tenants.assignUser({
  key: "user|123456789",
  email: "<mailto:john@smith.com|john@smith.com>",
  first_name: "John",
  last_name: "Smith",
  role_assignments: [
    {
      role: "Manager",
      tenant: "Marketing",
    },
  ],
}) ;
The API examples consistently show tenant assignment happening through role assignments. , (3) , (4) However, I don't have explicit documentation on whether the frontend allows adding a user to a tenant without a role, or what specific API endpoint enables that behavior. If you're seeing this capability in the frontend but can't find the corresponding API call, this might be worth clarifying with
To learn more, see the sources I considered: [[1] Concepts / Glossary](https://docs.permit.io/overview/glossary) [[2] Sync your First User](https://docs.permit.io/overview/sync-your-first-user-with-sdk) [[3] Sync Data into your App](https://docs.permit.io/overview/sync-applications-data) [[4] Fintech / Banking Demo Application](https://docs.permit.io/modeling/mesa-verde)
a
@Ask Inkeep I simply click edit on a user, switch their tenant, and save. It doesn't involve roles at all. Here's the API log for this action in the UI: Request: PATCH /v2/facts/.../.../users/j.martinez@example.com Body:
Copy code
{
  "email": "j.martinez@example.com",
  "first_name": "Jessica",
  "last_name": "Martinez"
}
Response:
Copy code
{
  "key": "j.martinez@example.com",
  "id": "...",
  "organization_id": "...",
  "project_id": "...",
  "environment_id": "...",
"associated_tenants": [
    {
      "tenant": "qa-17",
      "roles": [],
      "status": "pending",
      "resource_instance_roles": []
    }
  ],
  "roles": [],
  "created_at": "2026-03-01T09:14:53+00:00",
  "updated_at": "2026-03-01T09:14:53+00:00",
  "email": "j.martinez@example.com",
  "first_name": "Jessica",
  "last_name": "Martinez",
}
a
Thanks for tagging me @Aden McCusker. I'll look through relevant sources and get back to you shortly.
* Thanks for sharing that API log—this is really helpful context. Based on what you've shown, the response indicates the user has an
associated_tenants
entry with
"roles": []
(empty array) and
"status": "pending"
. (1) The documentation consistently states that "Users are assigned to tenants via a role" and shows tenant assignment happening through role assignments. , (2) However, the API response you captured shows something interesting: the user ​_is_​ associated with tenant
qa-17
but has no roles assigned (
"roles": []
) . This suggests there may be a mechanism to associate a user with a tenant without immediately assigning a role—though the
"status": "pending"
might indicate this is an intermediate state. I don't have explicit documentation covering this specific behavior—where a user can be added to a tenant's
associated_tenants
list without a role assignment via the PATCH endpoint. The documented approach for tenant assignment is through role assignments:
Copy code
await permit.api.assignRole({
  role: "AccountOwner",
  tenant: cleanedEmail,
  user: email,
}) ;
This looks like it could be undocumented API behavior or a frontend-specific workflow. To help clarify this and ensure our engineers can investigate: 1. Which Workspace / Project / Env are you using? 2. What's the exact sequence of UI actions you performed (which dropdown/field did you use to "switch their tenant") ? 3. Which PDP version are you running? 4. Could you share any relevant PDP logs if you've tested
permit.check()
with this user in the
qa-17
tenant? This would help determine whether this is intended behavior and how it interacts with permission checks.
Thanks for tagging me @Aden McCusker. I'll look through relevant sources and get back to you shortly.
To learn more, see the sources I considered: [[1] Multi-Tenant Authorization](https://docs.permit.io/concepts/multi-tenant-authorization) [[2] Fintech / Banking Demo Application](https://docs.permit.io/modeling/mesa-verde)
Thanks for sharing that detailed API log—this is really helpful context. Based on the response you captured, the user has an
associated_tenants
entry with
"roles": []
and
"status": "pending"
. (1) The documented approach consistently shows users being assigned to tenants ​_via roles_​:
Copy code
await permit.api.assignRole({
  role: "AccountOwner",
  tenant: cleanedEmail,
  user: email,
}) ;
(2) And the documentation states: "Users are assigned to tenants via a role." (1) The
"status": "pending"
in your response is notable—this typically indicates the assignment hasn't fully completed or synced. I don't have explicit documentation covering this specific UI workflow where a user gets added to
associated_tenants
without a role via the PATCH user endpoint. The body you showed only contains profile fields (
email
,
first_name
,
last_name
) , yet the response shows a new tenant association—suggesting the frontend may be making additional calls or there's undocumented behavior in how the UI handles tenant switching. To remove a user from a tenant entirely, there is a dedicated endpoint:
DELETE /v2/facts/{proj_id}/{env_id}/tenants/{tenant_id}/users/{user_id}
(3) This might be worth raising with the team to clarify the intended API behavior for adding a user t
To learn more, see the sources I considered: [[1] Multi-Tenant Authorization](https://docs.permit.io/concepts/multi-tenant-authorization) [[2] Fintech / Banking Demo Application](https://docs.permit.io/modeling/mesa-verde) [[3] DELETE /v2/facts/{proj_id}/{env_id}/tenants/{tenant_id}/users/{user_id}](https://api.permit.io/v2/redoc#tag/Tenants/operation/delete_tenant_user)
n
Hi @Aden McCusker - thanks for reaching out. Let me check with the teams and one of us will get back to you.
🙏 1
a
So yea I can definitely create a user via the UI with a key that matches a user that already exists in a different tenant, and this will update the pre-existing user and associate the new tenant with them - although I get a warning about assigning a tenant to a user without a role, but I can just continue anyway... why the warning? My goal is to be able to do this via the PDP.
And I see the UI is doing something pretty funky here Request POST
/v2/facts/.../.../role_assignments
Copy code
{
  "role": "__role_tenant_association",
  "tenant": "tenant1",
  "user": "test"
}
Response
Copy code
{
  "id": "...",
  "user": "test",
  "role": "tenant-association",
  "tenant": "tenant1",
  "resource_instance": null,
  "resource_instance_id": null,
  "user_id": "...",
  "role_id": "...",
  "tenant_id": "...",
  "organization_id": "...",
  "project_id": "...",
  "environment_id": "...",
  "created_at": "2026-03-06T23:09:21+00:00"
}
Interestingly, trying to create a user from the UI with a key that already exists but in a different tenant seems to send two requests to the backend - one tries to create the user again and fails with a
409
DUPLICATE_ENTITY
, and the other one is the above hacky request which associates the new tenant with the pre-existing user via the
role_assignments
endpoint, by setting
role
to
__role_tenant_association
d
Hi @Aden McCusker, just following up - The “tenant association” role is internal and does not affect anything besides of the UI. The user is only associated with the tenant without any actual roles on it. You are correct, you can add a user to a tenant without any role, both from the UI and using the API. The warning is just to make you aware that the user does not have any roles associated. Creating User without Roles UI - create a user without roles in a specific tenant view API - use Add User To Tenant API
✅ 1