Skip to main content
Permify supports multi-tenancy natively. A single Permify service can manage authorization for any number of independent applications or organizations (tenants). Each tenant has its own schema and its own relationship and attribute data, completely isolated from every other tenant. This makes Permify well-suited for SaaS platforms where each customer needs a customized authorization model, as well as for organizations that run multiple internal applications from a shared authorization service.
Tenant isolation is enforced at the data layer. Every relationship tuple, attribute value, and schema definition in the Permify database carries a tenant_id column. Permify will never return or evaluate data belonging to a different tenant, regardless of the permission being checked. The authorization schema for tenant A can be completely different from the schema for tenant B.

Default tenant

When you start a Permify service, a default tenant with id t1 is pre-created. If you are building a single-tenant application, you can use t1 for all requests without any tenant management overhead.

Tenant management

Permify exposes a dedicated Tenancy Service for creating, listing, and deleting tenants. All tenant management operations are available via REST and gRPC.
1

Create a tenant

cURL
curl --location --request POST 'localhost:3476/v1/tenants/create' \
--header 'Content-Type: application/json' \
--data-raw '{
  "id": "acme",
  "name": "Acme Corp"
}'
Response
{
  "tenant": {
    "id": "acme",
    "name": "Acme Corp",
    "created_at": "2024-01-15T10:00:00Z"
  }
}
The id you choose becomes the tenant_id used in every subsequent API call for that tenant.
2

Write the tenant's schema

Each tenant gets its own authorization schema. Schemas are scoped to the tenant and do not affect other tenants.
cURL
curl --location --request POST 'localhost:3476/v1/tenants/acme/schemas/write' \
--header 'Content-Type: application/json' \
--data-raw '{
  "schema": "entity user {} entity organization { relation admin @user relation member @user action view_files = admin or member action edit_files = admin action delete_file = admin }"
}'
Response
{
  "schema_version": "abc123"
}
3

Write relationship data for the tenant

Relationship tuples and attributes are always scoped to the tenant specified in the URL path.
cURL
curl --location --request POST 'localhost:3476/v1/tenants/acme/relationships/write' \
--header 'Content-Type: application/json' \
--data-raw '{
  "metadata": { "schema_version": "" },
  "tuples": [
    {
      "entity": { "type": "organization", "id": "1" },
      "relation": "admin",
      "subject": { "type": "user", "id": "alice" }
    },
    {
      "entity": { "type": "organization", "id": "1" },
      "relation": "member",
      "subject": { "type": "user", "id": "bob" }
    }
  ]
}'
4

Check permissions scoped to the tenant

Every permission check is scoped to a tenant via the tenant_id path parameter. The check is evaluated against only that tenant’s schema and data.
curl --location --request POST 'localhost:3476/v1/tenants/acme/permissions/check' \
--header 'Content-Type: application/json' \
--data-raw '{
  "metadata": {
    "snap_token": "",
    "schema_version": "",
    "depth": 20
  },
  "entity": { "type": "organization", "id": "1" },
  "permission": "delete_file",
  "subject": { "type": "user", "id": "alice" }
}'
Response
{
  "can": "RESULT_ALLOWED"
}
5

List or delete tenants

curl --location --request GET 'localhost:3476/v1/tenants/list' \
--header 'Content-Type: application/json' \
--data-raw '{
  "page_size": 20,
  "continuous_token": ""
}'
Deleting a tenant permanently removes all of its schemas, relationships, and attributes. This action cannot be undone.

How tenant isolation works in the database

Permify adds a tenant_id column to every authorization data table. No row is ever read or written without a tenant scope.
tables
├── migrations
├── relation_tuples      ← includes tenant_id column
├── schema_definitions   ← includes tenant_id column
├── tenants
├── transactions
The tenants table stores tenant metadata. The relation_tuples and schema_definitions tables store authorization data partitioned by tenant_id. A query for tenant acme will never touch rows belonging to tenant globex.

SaaS multi-tenant example

Consider a SaaS project management tool with two customers: Acme Corp and Globex Inc. Each customer has different roles and different users.
Acme Corp (acme)Globex Inc (globex)
Schemaadmin, member roles on projectowner, editor, viewer roles on project
Usersalice (admin), bob (member)carol (owner), dave (viewer)
Data isolation✅ Fully isolated✅ Fully isolated
Both tenants run on the same Permify service. Their schemas are independent — Acme’s schema change never affects Globex’s permissions. A check request to /v1/tenants/acme/permissions/check is evaluated entirely against Acme’s schema and data.
Tenant IDs are arbitrary strings you define. Use your existing customer or organization identifiers (e.g., a UUID, a slug) so you don’t need a separate mapping table.

Tenancy API reference

Full reference for the create, list, and delete tenant endpoints.

Building RBAC Systems

Model roles and permissions scoped to each tenant’s organization.

Snap tokens

Ensure consistent permission reads across replicated tenant data.

Enforcement

Overview of all permission check APIs and how to call them.

Build docs developers (and LLMs) love