Skip to main content

Zanzibar (Relationship-Based Authorization)

Zanzibar is the relationship-based authorization model described in Google's 2019 paper. Instead of assigning roles globally, Zanzibar stores relation tuples of the form object#relation@subject — for example document:readme#editor@user:alice ("alice is an editor of document readme"). An authorization check asks "is there a path in the relation graph from this subject to this object under this relation, given the schema's rewrite rules?".

Use Zanzibar when access is driven by hierarchy and relationships: folders containing files, teams owning projects, organizations containing workspaces. Inheritance ("editors of a folder can edit its documents") and sharing ("all members of this team can view this document") are natural in Zanzibar and awkward to express in RBAC (role-based access control).

Fine-grained authorization add-on

Relationship-based access control (feature zanzibar) is part of the Fine-grained authorization add-on — $99/month, available on the Pro and Business plans, together with ABAC (attribute-based access control). No plan includes it; buy it under Billing → Add-ons (see the pricing matrix). When billing is enforced (BILLING_ENFORCEMENT=enforce, the production default) and the organization doesn't have the add-on:

  • every check denies (allowed: false), including MCP per-tool grants;
  • the portal shows an upgrade notice on the Relationships pages, and creating or editing a namespace or creating a tuple redirects to the upgrade page;
  • AI policy authoring refuses a policy that contains relations with 403 and {"error": "feature_not_in_plan", "feature": "zanzibar", "upgrade": …}.

The RBAC → Zanzibar Sync from roles keeps working. Under BILLING_ENFORCEMENT=log checks are allowed and logged as billing.would_deny; an install with neither Stripe nor a license is not restricted.


What is Zanzibar?​

Zanzibar stores relationships between subjects and objects. Each relationship is a single tuple. A check walks the graph formed by those tuples, following the schema's userset rewrites.

Traditional RBACZanzibar
"Alice is an editor" (globally)"Alice is an editor of Document X"
"Bob is a viewer" (globally)"Bob is a member of Team A, and Team A has view access on Project Y"

This answers fine-grained, per-object questions:

  • "Can Alice edit this specific document?"
  • "Can Bob view this project because his team has access?"
  • "Who can access this file?"

Core Concepts​

Relation Tuples​

A relation tuple is the fundamental unit of Zanzibar. It describes a relationship between a subject and an object:

object#relation@subject
ComponentDescriptionExample
ObjectThe resource (namespace:id)document:readme
RelationThe type of relationshipeditor
SubjectA user or a usersetuser:alice or group:eng#member

Example tuples:

document:readme#editor@user:alice # Alice is editor of readme
document:readme#viewer@group:engineering # Engineering group can view readme
folder:docs#owner@user:bob # Bob owns the docs folder
team:backend#member@user:carol # Carol is a member of backend team

A subject can be a concrete identity (user:alice) or a userset — a reference to every entity holding a given relation on another object, written object#relation. For example group:engineering#member means "every user who is a member of group:engineering". Userset subjects let you grant access to whole groups in a single tuple.

Namespace Definitions​

A namespace defines the valid relations for a type of object and how each one is computed. The schema is a JSON object with a single relations key; the Create namespace form builds it for you, and its Document sharing example produces this:

{
"relations": {
"parent": { "_this": { "relation": "parent" } },
"owner": { "_this": { "relation": "owner" } },
"editor": {
"union": [
{ "computed_userset": { "relation": "owner" } },
{ "_this": { "relation": "editor" } }
]
},
"viewer": {
"union": [
{ "computed_userset": { "relation": "editor" } },
{ "_this": { "relation": "viewer" } },
{ "tuple_to_userset": {
"tupleset": { "relation": "parent" },
"computed_userset": { "relation": "viewer" } } }
]
}
}
}

This declares:

  • parent, owner, editor and viewer are valid relations for documents. _this means "the subjects stored directly in tuples for this relation".
  • editor = every owner (computed_userset) OR anyone stored as an editor.
  • viewer = every editor OR anyone stored as a viewer OR (tuple_to_userset) every viewer of the document's parent object, so a folder's viewers can see its documents.
  • Each relation is one of _this, computed_userset, tuple_to_userset, union or intersection; any other key is refused when the namespace is saved.

Check​

The core operation is Check — "does subject S have relation R on object O?" The engine searches the graph, following rewrite rules, and returns a yes/no. Run it from the portal Tester or via the Check API.

note

The original Zanzibar paper also describes an Expand operation ("the full tree of subjects holding a relation on an object"). LumoAuth exposes Check and Expand (POST /orgs/{orgId}/api/v1/zanzibar/expand with object and relation, returning a tree); the portal tester shows the same tree under Where it comes from. There is no list-objects operation.

Permission Inheritance​

Relations compose through the graph. Given:

folder:docs#owner@user:bob
folder:docs#parent@folder:root
document:readme#parent@folder:docs

and a schema rule "folder owners can view all documents in the folder", Bob can view document:readme — the check follows the parent edges up the tree.


Managing Zanzibar​

Portal​

In the sidebar, under Access control, open Relationships (/orgs/{orgId}/portal/zanzibar/). The page has four tabs:

  • Overview — counts of namespaces and stored tuples, and the engine status.
  • Namespaces — define object types and their relations (/zanzibar/namespaces). The list shows each namespace with its relations and when it was last updated; the row menu (...) edits or deletes it.
  • Tuples — create and manage relationships; Sync from roles generates tuples from your roles and permissions (/zanzibar/tuples).
  • Tester — run a Check ("does subject S have relation R on object O?") against the relation graph (/zanzibar/tester).

The header carries Relationship check (opens the Tester) and Create namespace.

The Relationships overview with counters, the Overview, Namespaces, Tuples and Tester tabs, and the namespace list

The Namespaces tab listing a namespace with its parent, owner, editor and viewer relations

Creating Relation Tuples​

A tuple needs a namespace with the relation you want to grant, so create the namespace first (Create namespace).

  1. Go to /orgs/{orgId}/portal/zanzibar/tuples (the Tuples tab).
  2. Click Add relationship.
  3. Enter:
    • Object namespace and Object ID — the resource (e.g., document and readme).
    • Relation — one of the relations defined on that namespace (e.g., editor).
    • User namespace and User ID — the subject (e.g., user and alice).
    • User relation — optional; set it to make the subject a userset (e.g., group, engineering and member for group:engineering#member).
  4. Click Add tuple. The tuple appears in the Tuples list, where Revoke relationship removes it again. Wildcards (*) are not accepted in the Object ID.

The Add relationship form with object namespace, object ID, relation and subject filled in

The Tuples tab listing stored relationships with their object, relation and subject, and the Sync from roles and Add relationship buttons

To verify the result, open the Tester tab, pick an object, a relation and a user from your stored tuples, and click Check permission. Quick tests fills the form from your tuples for you: Test as me and Random test.

Test access in Relationships mode, with an object, relation and user picked from the stored tuples and the Quick tests panel

Via API​

Tuples (writes) are managed through the portal — create them at /orgs/{orgId}/portal/zanzibar/tuples/create or generate them from your roles with the Sync from roles action. There is no public REST endpoint for tuple writes or list-objects.

The REST API exposes Check and Expand. The relation field takes any relation defined on the namespace; relations computed from others (editor above, which includes every owner) are resolved by the engine.

A caller may always check itself. Checking any other subject (another user, an agent or a userset) needs the authz.check permission or the authz:check API-key scope; otherwise the API answers 403 with "code": "insufficient_permissions".

# Check a relation (tenant-scoped)
curl -X POST https://your-domain.com/orgs/{orgId}/api/v1/zanzibar/check \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"object": "document:readme",
"relation": "editor",
"user": "user:alice"
}'
# Response: {"allowed": true, ...}

# Expand: who holds a relation on an object, as a tree
curl -X POST https://your-domain.com/orgs/{orgId}/api/v1/zanzibar/expand \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{"object": "document:readme", "relation": "viewer"}'
# Response: {"tree": {"type": "union", "object": "document:readme", "relation": "viewer", "children": [...], "subjects": [...]}}

There is also a global authorization route, POST /api/v1/authz/zanzibar/check, with the same object and relation fields but using subject instead of user:

curl -X POST https://your-domain.com/api/v1/authz/zanzibar/check \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"object": "document:readme",
"relation": "editor",
"subject": "user:alice"
}'
Canonical route + field names

The canonical Zanzibar check route is the organization-scoped POST /orgs/{orgId}/api/v1/zanzibar/check (used throughout this page). The other two are variants: POST /api/v1/zanzibar/check is a deprecated tenantless alias, and POST /api/v1/authz/zanzibar/check is the unscoped variant the JavaScript/TypeScript SDKs use. Prefer the canonical route.

Mind the field name: /orgs/{orgId}/api/v1/zanzibar/check and /api/v1/zanzibar/check use user, while /api/v1/authz/zanzibar/check uses subject. All three use relation (not permission). The wrong field name returns 400: /api/v1/authz/zanzibar/check answers with "code": "MISSING_FIELDS", the other two with an error message listing the missing fields.


Examples​

Google Drive-Style Sharing​

# Bob owns a folder
folder:shared-docs#owner@user:bob

# Alice can edit files in the folder
folder:shared-docs#editor@user:alice

# The engineering group can view
folder:shared-docs#viewer@group:engineering

# A document is in the folder
document:design-doc#parent@folder:shared-docs

Organization Hierarchy​

# Carol is a member of the backend team
team:backend#member@user:carol

# Backend team belongs to the engineering organization
org:engineering#team@team:backend

# Engineering org has access to the production environment
environment:production#accessor@org:engineering#member

# Carol can access production through: team → org → environment

Multi-Organization SaaS​

# Acme Corp's workspace
workspace:acme#admin@user:alice
workspace:acme#member@user:bob

# Project within the workspace
project:api-v2#parent@workspace:acme
project:api-v2#lead@user:bob

# Bob is project lead AND workspace member
# Alice is workspace admin, so she can access all projects

Zanzibar vs RBAC​

AspectRBACZanzibar
GranularityRole-levelObject-level
ScaleHundreds of rolesLarge relation graphs
Questions answered"What can this user do?""Can this user do X to object Y?"
MaintenanceManage roles and assignmentsManage relation tuples
Best forApplication-wide permissionsPer-resource permissions

Common mistakes​

  • Every check returns allowed: false although the tuple exists. The organization doesn't have the Fine-grained authorization add-on and billing is enforced. Add it under Billing → Add-ons (on Developer, upgrade to Pro first); the Relationships pages show the notice.
  • The Tuples and Namespaces pages show "Upgrade to enable" instead of "Add relationship" / "Create namespace". Same cause: new namespaces and tuples can't be added without the add-on, and opening the create pages by URL redirects to Billing with the add-on preselected.

Best Practices​

PracticeDescription
Design namespaces carefullyModel domain objects and their natural relationships
Use inheritance over duplicationPrefer parent relations to copying tuples
Grant to groups, not individualsUse userset subjects for team access
Test on the Tester tabVerify expected decisions at /orgs/{orgId}/portal/zanzibar/tester (Test access → Relationships) before rolling out
Start smallBegin with a few relations, extend as you learn the domain