Skip to Content

Roles & Permissions API

Auris implements a three-layer authorization model. This API covers two of those layers:

  1. RBAC (Role-Based Access Control): Roles are named sets of permissions. Users are assigned roles. Permissions use the format action:resource (e.g., view:invoices, manage:users). Each permission can be set to ALLOW, DENY, or INHERIT (tri-state model).

  2. FGA (Fine-Grained Authorization): A Zanzibar-compatible relationship-tuple engine for object-level access control. The FGA layer is used when role-level RBAC is insufficient — for example, “user Alice can view document 42 specifically, even though she doesn’t have view:all_documents.”


RBAC — Role Management

All role management endpoints require the manage:roles permission and the x-tenant header.

GET/api/rolesRequires: view:roles

List all roles defined in the tenant. Returns role metadata but not the full permission list. Use GET /api/roles/[id] or GET /api/roles/[id]/permissions for permission details.

Query parameters

ParameterTypeDescription
pageintegerPage number (default: 1)
limitintegerItems per page (default: 20)
searchstringFilter by role name

Success response

{ "ok": true, "data": { "data": [ { "id": "role_abc123", "name": "editor", "description": "Can create and modify content", "color": "#3b82f6", "userCount": 12, "createdAt": "2025-01-01T00:00:00Z" } ], "pagination": { "page": 1, "limit": 20, "total": 5, "totalPages": 1 } } }

POST/api/rolesRequires: manage:roles

Create a new role. Role names must be unique within the tenant and may only contain alphanumeric characters, hyphens, and underscores.

Request body

{ "name": "billing-admin", "description": "Manages invoices and payment methods", "color": "#f59e0b" }

description and color are optional.

Success response

{ "ok": true, "data": { "id": "role_def456", "name": "billing-admin", "description": "Manages invoices and payment methods", "color": "#f59e0b", "createdAt": "2025-02-18T10:00:00Z" } }

Error codes

CodeHTTPDescription
NAME_TAKEN409A role with this name already exists
VALIDATION_ERROR400Invalid role name format

GET/api/roles/[id]Requires: manage:roles

Get a role by ID, including its full permission list with the ALLOW/DENY state for each permission.

Success response

{ "ok": true, "data": { "id": "role_abc123", "name": "editor", "description": "Can create and modify content", "color": "#3b82f6", "permissions": [ { "id": "perm_1", "key": "view:invoices", "state": "ALLOW" }, { "id": "perm_2", "key": "create:invoices", "state": "ALLOW" }, { "id": "perm_3", "key": "delete:invoices", "state": "DENY" } ] } }

Permission states: ALLOW (explicitly granted), DENY (explicitly blocked), INHERIT (not explicitly set — defaults to the system default, typically deny).


PUT/api/roles/[id]Requires: manage:roles

Update a role’s name, description, or color.

Request body

{ "description": "Can create, modify, and publish content", "color": "#8b5cf6" }

Success response

{ "ok": true, "data": { "id": "role_abc123", "name": "editor", "description": "Can create, modify, and publish content", "color": "#8b5cf6" } }

DELETE/api/roles/[id]Requires: manage:roles

Delete a role. Users who have this role assigned will lose it immediately. The role is removed from all users and then deleted from the tenant.

Deleting a role affects all users who have it. Verify impact using GET /api/roles/[id] which includes userCount before deleting.

Success response

{ "ok": true, "data": { "deleted": true } }

RBAC — Permission Management

GET/api/roles/[id]/permissionsRequires: view:roles

List the permission settings for a role, grouped by category. Each permission has a state of ALLOW, DENY, or INHERIT.

Success response

{ "ok": true, "data": { "permissions": [ { "category": "Documents", "items": [ { "id": "perm_1", "key": "view:invoices", "label": "View Invoices", "state": "ALLOW" }, { "id": "perm_2", "key": "create:invoices", "label": "Create Invoices", "state": "ALLOW" }, { "id": "perm_3", "key": "delete:invoices", "label": "Delete Invoices", "state": "INHERIT" } ] } ] } }

PUT/api/roles/[id]/permissionsRequires: manage:roles

Update the permission states for a role. Send an array of permission state objects. Permissions not included in the array are left unchanged.

Request body

{ "permissions": [ { "permissionId": "perm_1", "state": "ALLOW" }, { "permissionId": "perm_3", "state": "DENY" } ] }

Success response

{ "ok": true, "data": { "updated": 2, "permissions": [ { "id": "perm_1", "key": "view:invoices", "state": "ALLOW" }, { "id": "perm_3", "key": "delete:invoices", "state": "DENY" } ] } }

Permission Check

POST/api/roles/checkRequires: authenticated user

Check whether the currently authenticated user has a set of permissions. Resolves permissions through the full RBAC stack: direct user overrides, role assignments, and default policies. Optionally scoped to a specific application.

This endpoint is used by resource servers (including the Auris Dashboard) to enforce authorization before performing operations.

Request body

{ "permissions": ["view:invoices", "create:invoices", "approve:expenses"], "applicationId": "app_abc123" }

applicationId is optional. When provided, only permissions configured for that application’s scope are checked.

Success response

{ "ok": true, "data": { "permissions": { "view:invoices": true, "create:invoices": true, "approve:expenses": false } } }

Error codes

CodeHTTPDescription
VALIDATION_ERROR400permissions is not an array of strings

FGA — Authorization Models

The Fine-Grained Authorization engine uses a DSL-based model to define object types, relations, and rewrite rules. Before writing tuples, you must create and activate an authorization model.

All FGA endpoints require the x-tenant header.

GET/api/fga/modelsRequires: manage:fga_models

List all authorization models for the tenant. Only one model can be active at a time.

Success response

{ "ok": true, "data": [ { "id": "model_abc123", "name": "SaaS Authorization Model", "version": 3, "isActive": true, "createdAt": "2025-02-10T00:00:00Z" } ] }

POST/api/fga/modelsRequires: manage:fga_models

Create a new authorization model by providing a DSL definition. The DSL is parsed and validated before storage. If validation fails, a detailed error message is returned.

Request body

{ "name": "Document Access Model", "dsl": "type user\n\ntype document\n relations\n define owner: [user]\n define viewer: [user] or owner\n" }

Success response

{ "ok": true, "data": { "id": "model_def456", "name": "Document Access Model", "version": 1, "isActive": false, "schema": { "typeDefinitions": [ { "type": "user", "relations": {} }, { "type": "document", "relations": { "owner": { "this": {} }, "viewer": { "union": {} } } } ] } } }

Error codes

CodeHTTPDescription
DSL_PARSE_ERROR400DSL syntax is invalid — error includes line number and description
DSL_VALIDATION_ERROR400DSL is syntactically valid but references undefined types or relations

GET/api/fga/models/[id]Requires: view:fga_models

Get a specific authorization model, including its full parsed schema and DSL text.


PUT/api/fga/models/[id]Requires: manage:fga_models

Update a model’s name or DSL. The DSL is re-parsed and validated on update.


POST/api/fga/models/[id]/activateRequires: manage:fga_models

Set this model as the active authorization model for the tenant. Deactivates any previously active model. All subsequent check, expand, and list-objects calls use this model.

Request: No body required.

Success response

{ "ok": true, "data": { "activated": true, "modelId": "model_def456" } }

FGA — Relationship Tuples

Tuples are the facts of the authorization system. Each tuple asserts that a subject has a relation to an object.

Tuple format: objectType:objectId#relation@subjectType:subjectId

Example: document:readme#viewer@user:alice — user alice is a viewer of document readme.

GET/api/fga/tuplesRequires: view:fga_tuples

List relationship tuples, with optional filtering.

Query parameters

ParameterTypeDescription
objectTypestringFilter by object type (e.g., document)
objectIdstringFilter by object ID
relationstringFilter by relation name
subjectTypestringFilter by subject type
subjectIdstringFilter by subject ID
pageintegerPage number
limitintegerItems per page

Success response

{ "ok": true, "data": { "data": [ { "id": "tuple_abc", "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "user", "subjectId": "alice", "createdAt": "2025-02-15T10:00:00Z" } ], "pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 } } }

POST/api/fga/tuplesRequires: manage:fga_tuples

Write a single relationship tuple. The tuple is validated against the active authorization model before storage.

Request body

{ "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "user", "subjectId": "alice" }

For subject-set references (e.g., “all members of group engineering can view”):

{ "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "group", "subjectId": "engineering", "subjectRelation": "member" }

Success response

{ "ok": true, "data": { "id": "tuple_abc", "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "user", "subjectId": "alice" } }

Error codes

CodeHTTPDescription
NO_ACTIVE_MODEL400No active authorization model to validate against
INVALID_RELATION400Relation does not exist on this object type in the active model
TUPLE_EXISTS409An identical tuple already exists (idempotent write preferred — use bulk)

DELETE/api/fga/tuplesRequires: manage:fga_tuples

Delete a specific relationship tuple by providing the tuple data in the request body.

Request body

{ "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "user", "subjectId": "alice" }

Success response

{ "ok": true, "data": { "deleted": true } }

POST/api/fga/tuples/bulkRequires: manage:fga_tuples

Write or delete multiple tuples in a single request. Operations are processed atomically — if any operation fails validation, the entire bulk request is rejected.

Request body

{ "writes": [ { "objectType": "document", "objectId": "readme", "relation": "editor", "subjectType": "user", "subjectId": "bob" } ], "deletes": [ { "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "user", "subjectId": "alice" } ] }

Success response

{ "ok": true, "data": { "written": 1, "deleted": 1 } }

FGA — Authorization Queries

POST/api/fga/checkRequires: debug:fga

Check whether a subject has a specific relation to an object. Evaluates the full rewrite rules recursively. Optionally returns the resolution tree for debugging.

Request body

{ "objectType": "document", "objectId": "readme", "relation": "viewer", "subjectType": "user", "subjectId": "alice", "explain": true }

Set explain: true to receive the resolution tree (useful for debugging why a check passed or failed).

Success response

{ "ok": true, "data": { "allowed": true, "resolution": { "type": "union", "result": true, "children": [ { "type": "this", "relation": "viewer", "result": true, "tupleFound": "document:readme#viewer@user:alice" } ] } } }

POST/api/fga/expandRequires: debug:fga

Expand a relation to list all subjects (users or user sets) that have a given relation to an object. Returns a tree structure that follows the rewrite rules.

Request body

{ "objectType": "document", "objectId": "readme", "relation": "viewer" }

Success response

{ "ok": true, "data": { "tree": { "root": { "type": "union", "nodes": [ { "type": "leaf", "subjects": [ { "type": "user", "id": "alice" }, { "type": "user", "id": "bob" } ] }, { "type": "computed_userset", "relation": "owner", "subjects": [{ "type": "user", "id": "charlie" }] } ] } } } }

POST/api/fga/list-objectsRequires: debug:fga

List all objects of a given type that a subject can access via a specific relation. Uses reverse lookup through the tuple store and rewrite rules.

Request body

{ "objectType": "document", "relation": "viewer", "subjectType": "user", "subjectId": "alice" }

Success response

{ "ok": true, "data": { "objectIds": ["readme", "api-spec", "changelog"], "total": 3 } }

The check, expand, and list-objects endpoints require debug:fga because they expose the internal authorization model structure. In production, resource servers should call these endpoints using an M2M token with this permission rather than exposing them to end users.