Roles & Permissions API
Auris implements a three-layer authorization model. This API covers two of those layers:
-
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 toALLOW,DENY, orINHERIT(tri-state model). -
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.
/api/rolesRequires: view:rolesList 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
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (default: 1) |
limit | integer | Items per page (default: 20) |
search | string | Filter 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 }
}
}/api/rolesRequires: manage:rolesCreate 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
| Code | HTTP | Description |
|---|---|---|
NAME_TAKEN | 409 | A role with this name already exists |
VALIDATION_ERROR | 400 | Invalid role name format |
/api/roles/[id]Requires: manage:rolesGet 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).
/api/roles/[id]Requires: manage:rolesUpdate 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"
}
}/api/roles/[id]Requires: manage:rolesDelete 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
/api/roles/[id]/permissionsRequires: view:rolesList 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" }
]
}
]
}
}/api/roles/[id]/permissionsRequires: manage:rolesUpdate 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
/api/roles/checkRequires: authenticated userCheck 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
| Code | HTTP | Description |
|---|---|---|
VALIDATION_ERROR | 400 | permissions 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.
/api/fga/modelsRequires: manage:fga_modelsList 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"
}
]
}/api/fga/modelsRequires: manage:fga_modelsCreate 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
| Code | HTTP | Description |
|---|---|---|
DSL_PARSE_ERROR | 400 | DSL syntax is invalid — error includes line number and description |
DSL_VALIDATION_ERROR | 400 | DSL is syntactically valid but references undefined types or relations |
/api/fga/models/[id]Requires: view:fga_modelsGet a specific authorization model, including its full parsed schema and DSL text.
/api/fga/models/[id]Requires: manage:fga_modelsUpdate a model’s name or DSL. The DSL is re-parsed and validated on update.
/api/fga/models/[id]/activateRequires: manage:fga_modelsSet 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.
/api/fga/tuplesRequires: view:fga_tuplesList relationship tuples, with optional filtering.
Query parameters
| Parameter | Type | Description |
|---|---|---|
objectType | string | Filter by object type (e.g., document) |
objectId | string | Filter by object ID |
relation | string | Filter by relation name |
subjectType | string | Filter by subject type |
subjectId | string | Filter by subject ID |
page | integer | Page number |
limit | integer | Items 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 }
}
}/api/fga/tuplesRequires: manage:fga_tuplesWrite 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
| Code | HTTP | Description |
|---|---|---|
NO_ACTIVE_MODEL | 400 | No active authorization model to validate against |
INVALID_RELATION | 400 | Relation does not exist on this object type in the active model |
TUPLE_EXISTS | 409 | An identical tuple already exists (idempotent write preferred — use bulk) |
/api/fga/tuplesRequires: manage:fga_tuplesDelete 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 }
}/api/fga/tuples/bulkRequires: manage:fga_tuplesWrite 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
/api/fga/checkRequires: debug:fgaCheck 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"
}
]
}
}
}/api/fga/expandRequires: debug:fgaExpand 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" }]
}
]
}
}
}
}/api/fga/list-objectsRequires: debug:fgaList 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.
Related
- Roles & Permissions Guide — How RBAC works in Auris
- Fine-Grained Authorization — Advanced authorization with FGA
- Users & Roles — Assign roles from the Console
- Fine-Grained Authorization API — Zanzibar-style authorization checks