Skip to main content

Tenants

A tenant represents an organization or group. Tenants contain users and can be granted permissions on datasets, which apply to all members. This enables organization-wide access control and simplifies permission management for teams.
Tenant-level permissions — When a tenant is granted a permission on a dataset, all users in that tenant automatically inherit that permission.

Tenant Concept

Tenants are created by users who become the tenant owner. The owner, or anyone holding the manage_users capability in the tenant, can add other users to the tenant. A user can be a member of several tenants, but acts inside exactly one of them at a time — their active tenant — and tenants can contain multiple users.

Tenant-Level Permissions

When a tenant is granted a permission on a dataset, all users in that tenant automatically inherit that permission. This happens through the permission checking mechanism: get_all_user_permission_datasets() unions the user’s direct permissions with their tenant’s permissions. Tenants start with zero permissions. You can leave the tenant principal empty and manage access purely through individual user permissions, or grant tenant-wide permissions for organization-wide resources.

Permission Inheritance

Tenant-level grants are blanket: once a dataset permission is assigned to the tenant principal, every user whose tenant_id matches inherits it. Users can also receive direct permissions that differ from the tenant defaults, giving you flexibility to customize access for specific users within the same tenant.

Active Tenant and Search Scope

The active tenant is the tenant_id on the user row, and it scopes every dataset lookup:
  • A dataset is stamped with the creating user’s active tenant when it is created, and that stamp never changes. A dataset created while no tenant was active belongs to its owner personally.
  • get_all_user_permission_datasets() first unions the user’s direct grants with the tenant and role grants from every tenant they are a member of, and then drops every dataset whose tenant does not match the caller’s active tenant. The filter runs after the union, so a matching ACL row on its own is not enough.
Because of that filter a single recall() never spans two tenants. Sharing a dataset with a user whose active tenant is a different one has no effect while that tenant stays active: an unscoped recall silently omits the dataset, and naming it through dataset_ids raises PermissionDeniedError (403) — the same error as a dataset that was never granted at all. The filter cuts both ways: while a tenant is active, the user’s own personal datasets are out of scope too, because their tenant_id is null and the active one is not. That is the same constraint the multi-tenant walkthrough runs into when a personal dataset cannot be granted to a role inside a tenant. To read datasets in another tenant, the user must be a member of it and switch the active tenant, then search again — one recall per tenant:
select_tenant returns the user object carrying the new active tenant — pass that one to the next call, not the stale copy. Re-reading the user with get_user(user.id) afterwards is equivalent and is what the multi-tenant walkthrough does; either way, a user object captured before the switch still points at the old tenant. select_tenant raises TenantNotFoundError if the user is not a member of the target tenant, and tenant_id=None returns them to their personal, tenant-less scope. Over HTTP the equivalents are POST /api/v1/permissions/tenants/select and GET /api/v1/permissions/tenants/me, which lists the tenants the caller belongs to.
The Tenant model defines what gets stored in the SQL database. The tenants table contains:
  • id: Unique identifier (UUID primary key, references principals.id)
  • name: Human-readable name (unique)
  • owner_id: ID of the user who created the tenant
  • create_tenant(tenant_name, user_id): Creates a new tenant with the specified user as owner
  • add_user_to_tenant(user_id, tenant_id, owner_id): Adds an existing user to a tenant. Requires the manage_users capability in that tenant; the tenant owner always qualifies.
  • remove_user_from_tenant(user_id, tenant_id, owner_id): Removes a user from the tenant. Also strips them from all roles in the tenant, revokes their permissions on tenant datasets, and removes the capabilities granted to them directly in that tenant, so removing and re-adding someone cannot restore capabilities. The tenant owner cannot be removed. Exposed via DELETE /v1/permissions/tenants/{tenant_id}/users/{user_id}. The removal also covers the agents the user created (users whose parent_user_id points at them) including any sub-agents created before agents were limited to one level: their memberships, roles, dataset permissions and capabilities in the tenant are removed the same way. Agents whose active tenant is this one also lose their API keys. An agent active in a different tenant keeps its keys, because it cannot reach this tenant. If this tenant was the active tenant of the user or any of those agents, their tenant_id is cleared, so creating a new API key or a new dataset does not put them back in it. To act in the tenant again, the user must be re-added and then select it with select_tenant, or be re-added with add_user_to_tenant(..., set_as_active_tenant=True). Re-adding the user does not re-add their agents; each agent must be added back on its own, and an agent that lost its keys needs a new API key.
Tenant deletion is not yet supported. There is no delete_tenant API or function. As a workaround, depopulate the tenant by calling remove_user_from_tenant for every non-owner member, then stop using the tenant.
  • Users without a tenant exist but are isolated
  • API endpoints for tenant management

Tenant-Level Capabilities

Besides dataset permissions, a tenant principal can be granted capabilities — tenant-scoped actions such as manage_users. A grant made to the tenant principal reaches every current and future member, which makes it the blunt instrument of the three levels; prefer a role or a direct user grant unless the action really should be open to everyone in the organization. The tenant owner holds the whole capability catalog implicitly, with no row stored for them. That is how the first grant in a tenant gets made, and it means the owner cannot be revoked out of their own tenant.
For a user principal, the grant is scoped by the tenant_id query parameter, which defaults to the caller’s current tenant — a capability granted to someone never applies in every tenant they belong to. For a role or a tenant principal the parameter is ignored, since those carry their own tenant. The full rules for granting and revoking are on the permissions overview.

Reading Your Own Capabilities

GET /api/v1/permissions/tenants/{tenant_id}/capabilities/me returns the capabilities the authenticated caller resolves to in that tenant:
The caller must belong to the tenant. A tenant they are not a member of and one that does not exist both answer 403, so the endpoint cannot be used to discover which tenant ids are real. The response lists only capabilities actually granted (or the whole catalog for the owner); it does not reflect the deprecated admin role-name fallback, so a member of an admin role with no capability rows sees an empty list even though they pass every capability check.
This endpoint exists so a client can decide which controls to show. It is not an authorization boundary on its own — every endpoint still enforces its own capability, because anyone who knows a URL can call it without going through your UI.

Tenant Management

Tenant owners can:
  • Add users to the tenant
  • Remove users from the tenant
  • Grant permissions to the tenant principal
  • Manage tenant-level access to datasets
Membership administration is not owner-only, though. Adding and removing users are both gated on the manage_users capability in the tenant — adding used to be restricted to the owner and now follows the same rule as removal — and that capability can be granted to a role, to the tenant principal, or to a user directly. That is how you delegate day-to-day membership administration without handing over ownership.

Use Cases

Tenants are ideal for:
  • Organization-wide access — Grant broad permissions to all team members
  • Department-level isolation — Keep different departments’ data separate
  • Project-based grouping — Organize users around specific projects or initiatives
  • Scalable permission management — Avoid granting individual permissions to many users

Data Isolation

Each tenant’s data is completely isolated:
  • Database separation — Each user’s data is stored in their own directory
  • Permission boundaries — Users can only access datasets they have permissions for
  • No cross-tenant access — Data from one tenant cannot be accessed while another tenant is active, however it was granted — see Active Tenant and Search Scope

Best Practices

  • Start with tenant-level permissions — Grant broad access at the tenant level
  • Refine with user permissions — Override tenant defaults for specific users when needed
  • Use roles for granular control — Create roles within tenants for more specific access patterns
  • Regular permission audits — Review and update permissions as team structure changes

Roles

Learn about role-based permissions within tenants

ACL

Understand how permissions are stored and checked