The Authorization tab is where you decide who can do what. It's the second-most-used part of the dashboard after Users — it's where you define the rules, and where you investigate "why can't this user do this?" when something goes wrong.
# Run a permission check from your backendcurl -X POST https://your-app.authaz.io/api/v1/authorization/check \ -H "X-API-Key: $AUTHAZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "userId": "user_01h...", "resource": "invoices", "action": "create", "tenantId": "tenant_01h..." }'
{ "allowed": true }
Sub-millisecond. That's the whole API surface for runtime checks; the rest of this page covers what you set up beforehand.
Roles → Policies → Permissions. A role attaches one or more policies; a policy bundles permissions; a permission is a resource:action pair. Assigning a user a role gives them every permission inside every attached policy.
Direct grants (ReBAC). A user can be granted a permission on a specific resource — "this user can read this specific document" — without going through a role. Use this when role-based scoping isn't precise enough.
Both styles work simultaneously and the engine returns a single yes/no answer with a full reasoning trace.
The order matches the recommended workflow: define resources first (you can't grant a permission that doesn't exist), build policies from those permissions, compose roles from those policies, assign roles to users — and use the Access Explorer to debug when something doesn't behave the way you expect.
Roles can be global (apply across every tenant in the application) or tenant-scoped (only effective inside one tenant). The distinction shows up everywhere:
The Roles page groups roles into "Global" and "Tenant" sections.
A user can be Owner in tenant A and Viewer in tenant B — same identity, different roles per scope.
Permission checks include tenantId and the engine considers both the user's global roles and their tenant-scoped roles.
The user can now read (anything viewer is allowed) on that one document. Permission checks against documents:read for resourceId: doc_01h... will return allowed: true without the user needing any role.
Zeratul is built for the read path: caches are aggressive, denormalized indexes are kept in sync as you mutate roles, and the typical check returns from in-memory state. Even on cold caches, p99 sits in single-digit milliseconds.
For mutation-heavy workloads (bulk role-assignments, large permission imports), use the bulk endpoints — they batch internally and avoid roundtrip thrash.