Skip to content
Beta — Truss is in public beta. Documentation is actively updated but may not reflect the latest changes. Report issues on GitHub.

Authorization

Truss provides relation-based access control (ReBAC) powered by Ory Keto. This lets you define who can do what to which resource using relation tuples — a flexible model that handles RBAC, ReBAC, and ACL patterns. The dashboard includes a permission checker playground, role matrix, relationship graph, OPL editor with version history, and model templates.

Setup

KETO_READ_URL=http://localhost:4466
KETO_WRITE_URL=http://localhost:4467
KETO_ADMIN_TOKEN=your-admin-token

Concepts

Authorization in Truss is based on relation tuples:

namespace:object#relation@subject

For example:

  • Organization:acme#member@user123 — user123 is a member of Acme org
  • Project:website#editor@user456 — user456 can edit the website project
  • Project:website#viewer@Organization:acme#member — all Acme members can view the website

Namespaces

Namespaces define the types of resources in your system. Each namespace has its own set of relations and permissions.

Truss ships with three default namespaces:

  • User — individual users
  • Organization — groups with members and admins
  • Project — resources with owners, editors, and viewers

You can define custom namespaces using the OPL (Ory Permission Language) editor in the dashboard.

Tenant Isolation

All authorization data is tenant-scoped. Namespaces are internally prefixed with a tenant identifier so tenants cannot see each other’s tuples. This prefixing is transparent — the dashboard and API show clean namespace names without prefixes.


Core Permission Engine

Relationship Tuple CRUD

Create, list, and delete relation tuples. Tuples are the fundamental building blocks of your permission model.

Dashboard: Authorization > Permissions tab

API Endpoints:

MethodPathDescription
GET/api/keto/relation-tuplesList tuples with filters
PUT/api/keto/relation-tuplesCreate a relation tuple
DELETE/api/keto/relation-tuplesDelete a relation tuple
const TRUSS_URL = "http://localhost:8787";
// Create a relation tuple (grant access)
await fetch(`${TRUSS_URL}/api/keto/relation-tuples`, {
method: "PUT",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
namespace: "Project",
object: "my-project",
relation: "editor",
subject_id: "user-123",
}),
});
// List tuples for a resource
const tuples = await fetch(
`${TRUSS_URL}/api/keto/relation-tuples?namespace=Project&object=my-project`,
{ credentials: "include" }
).then(r => r.json());
// { relation_tuples: [...], next_page_token: "..." }
// Delete a relation tuple (revoke access)
await fetch(
`${TRUSS_URL}/api/keto/relation-tuples?namespace=Project&object=my-project&relation=editor&subject_id=user-123`,
{ method: "DELETE", credentials: "include" }
);

Query parameters for listing tuples:

ParameterDescription
namespaceFilter by namespace
objectFilter by object ID
relationFilter by relation name
subject_idFilter by subject ID
subject_set.namespaceFilter by subject set namespace
subject_set.objectFilter by subject set object
subject_set.relationFilter by subject set relation
page_tokenCursor for pagination
page_sizeNumber of tuples per page

Permission Check

Answer the question: “Is user X allowed to do Y on resource Z?” The check evaluates both direct tuples and indirect permissions through subject sets.

Dashboard: Authorization > Permissions tab > Check panel

API Endpoint:

MethodPathDescription
POST/api/keto/checkCheck a single permission

Every permission check is logged to the audit trail.

// Check if a user has permission
const res = await fetch(`${TRUSS_URL}/api/keto/check`, {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
namespace: "Project",
object: "my-project",
relation: "editor",
subject_id: "user-123",
}),
});
const { allowed } = await res.json();
// allowed: true | false
// Check with a subject set (group membership)
const groupCheck = await fetch(`${TRUSS_URL}/api/keto/check`, {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
namespace: "Project",
object: "my-project",
relation: "viewer",
subject_set: {
namespace: "Organization",
object: "acme",
relation: "member",
},
}),
}).then(r => r.json());

Permission Expand (Access Tree)

Show the full access tree for a permission, including union, intersection, and leaf nodes. This answers “why is this allowed?” by revealing the entire permission evaluation path.

Dashboard: Authorization > Permissions tab > Expand panel (tree visualization)

API Endpoint:

MethodPathDescription
GET/api/keto/expandExpand a permission tree

Query parameters:

ParameterDescription
namespaceThe namespace to expand
objectThe object to expand
relationThe relation to expand
max-depthMaximum tree depth (default: 5)
// Expand permission tree
const tree = await fetch(
`${TRUSS_URL}/api/keto/expand?namespace=Project&object=my-project&relation=view&max-depth=5`,
{ credentials: "include" }
).then(r => r.json());
// Response structure:
// {
// "type": "union",
// "subject_set": { "namespace": "Project", "object": "my-project", "relation": "view" },
// "children": [
// { "type": "leaf", "subject_id": "user-123" },
// {
// "type": "union",
// "subject_set": { "namespace": "Organization", "object": "acme", "relation": "member" },
// "children": [
// { "type": "leaf", "subject_id": "user-456" },
// { "type": "leaf", "subject_id": "user-789" }
// ]
// }
// ]
// }

Reverse Lookup

Answer the question: “Who can access resource X?” Returns all subjects with access to a given object, their relations, and a permission matrix.

Dashboard: Authorization > Permissions tab > “Who can access?” panel

API Endpoints:

MethodPathDescription
POST/api/keto/who-can-accessGet all subjects with access to an object
GET/api/keto/subject-tuples/:subjectIdGet all tuples where a subject has access
// Who can access this object?
const access = await fetch(`${TRUSS_URL}/api/keto/who-can-access`, {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
namespace: "Project",
object: "my-project",
}),
}).then(r => r.json());
// Returns subjects, their relations, and a permission matrix
// What can this subject access?
const tuples = await fetch(
`${TRUSS_URL}/api/keto/subject-tuples/user-123`,
{ credentials: "include" }
).then(r => r.json());
// Returns all tuples where user-123 is the subject, across all namespaces

Batch Tuple Operations

Bulk create or delete tuples in a single API call. Useful for provisioning access for new teams or cleaning up permissions.

Dashboard: Authorization > Permissions tab > Import/Export buttons

API Endpoints:

MethodPathDescription
POST/api/keto/relation-tuples/importBulk import tuples (up to 500)
POST/api/keto/relation-tuples/batch-deleteBulk delete tuples
// Bulk import tuples
await fetch(`${TRUSS_URL}/api/keto/relation-tuples/import`, {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
tuples: [
{ namespace: "Project", object: "website", relation: "viewer", subject_id: "user-1" },
{ namespace: "Project", object: "website", relation: "viewer", subject_id: "user-2" },
{ namespace: "Project", object: "website", relation: "editor", subject_id: "user-3" },
],
}),
});
// Bulk delete tuples
await fetch(`${TRUSS_URL}/api/keto/relation-tuples/batch-delete`, {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
tuples: [
{ namespace: "Project", object: "website", relation: "viewer", subject_id: "user-1" },
{ namespace: "Project", object: "website", relation: "viewer", subject_id: "user-2" },
],
}),
});

Tuple Import / Export

Download all tuples as JSON for backup or migration, or upload a JSON file to restore them.

Dashboard: Authorization > Permissions tab > Import / Export buttons

Export returns all relation tuples for the current tenant as a JSON array. Import accepts the same format and creates all tuples.

Terminal window
# Export tuples (download all as JSON)
curl "http://localhost:8787/api/keto/relation-tuples?page_size=1000" \
-H "Cookie: truss_session=your-session-token" \
-o tuples.json
# Import tuples (upload JSON)
curl -X POST http://localhost:8787/api/keto/relation-tuples/import \
-H "Content-Type: application/json" \
-H "Cookie: truss_session=your-session-token" \
-d @tuples.json

Namespace Listing

Browse all namespaces defined in your permission model. Namespaces are tenant-scoped — you only see your own.

Dashboard: Authorization > Namespaces tab

API Endpoint:

MethodPathDescription
GET/api/keto/namespacesList all namespaces for the current tenant
Terminal window
curl http://localhost:8787/api/keto/namespaces \
-H "Cookie: truss_session=your-session-token"
# { "namespaces": [{ "name": "User" }, { "name": "Organization" }, { "name": "Project" }] }

Access Control Models

Truss supports three access control models, all built on the same relation tuple foundation.

RBAC (Role-Based Access Control)

Define roles (admin, editor, viewer) and assign users to roles. The dashboard provides a visual role matrix grid where you can see all users and their roles at a glance.

Dashboard: Authorization > Roles tab

The role matrix shows:

  • Rows: Users (populated from Kratos identity list via user picker)
  • Columns: Roles defined in your namespace
  • Cells: Click to assign/unassign a role

Assigning a role is creating a tuple:

Terminal window
# Assign "admin" role to user-123 in Organization:acme
curl -X PUT http://localhost:8787/api/keto/relation-tuples \
-H "Content-Type: application/json" \
-H "Cookie: truss_session=your-session-token" \
-d '{
"namespace": "Organization",
"object": "acme",
"relation": "admin",
"subject_id": "user-123"
}'

Example RBAC OPL model:

class Organization implements Namespace {
related: {
admins: User[]
members: User[]
viewers: User[]
}
permits = {
manage: (ctx: Context) => this.related.admins.includes(ctx.subject),
edit: (ctx: Context) => this.permits.manage(ctx) || this.related.members.includes(ctx.subject),
view: (ctx: Context) => this.permits.edit(ctx) || this.related.viewers.includes(ctx.subject),
}
}

ReBAC (Relationship-Based Access Control)

The core Keto model. Permissions are derived from relationships between entities. A user’s access is determined by traversing the relationship graph.

Key concept: Subject sets. Instead of granting access to individual users, you can grant access to an entire group:

{
"namespace": "Project",
"object": "website",
"relation": "viewer",
"subject_set": {
"namespace": "Organization",
"object": "acme",
"relation": "member"
}
}

This means: anyone who is a member of Organization:acme is automatically a viewer of Project:website. When you add a new member to the organization, they immediately gain access to the project — no additional tuples needed.

ReBAC supports:

  • Hierarchical permissions (admin implies editor implies viewer)
  • Group-based access (organization members, team members)
  • Resource inheritance (folder permissions cascade to files)
  • Cross-namespace relationships

ACL (Access Control Lists)

Direct tuple assignments for simple access control. Each tuple is a direct grant of a specific permission to a specific user on a specific resource.

Terminal window
# Direct ACL: user-123 can view document-456
curl -X PUT http://localhost:8787/api/keto/relation-tuples \
-H "Content-Type: application/json" \
-H "Cookie: truss_session=your-session-token" \
-d '{
"namespace": "Document",
"object": "document-456",
"relation": "viewer",
"subject_id": "user-123"
}'
# Direct ACL: user-456 can edit document-456
curl -X PUT http://localhost:8787/api/keto/relation-tuples \
-H "Content-Type: application/json" \
-H "Cookie: truss_session=your-session-token" \
-d '{
"namespace": "Document",
"object": "document-456",
"relation": "editor",
"subject_id": "user-456"
}'

ACLs are the simplest model — every permission is an explicit tuple. They work well for small-scale systems but can become unwieldy as user and resource counts grow. Consider RBAC or ReBAC for larger systems.


Dashboard & Tools

Permission Checker Playground

Interactive tool for testing permissions. Enter a namespace, object, relation, and subject, then check if the permission is allowed. The playground also supports expanding the access tree to visualize why a permission is granted or denied.

Dashboard: Authorization > Permissions tab > Check panel

The playground provides:

  • Check mode — “Is this allowed?” returns true/false
  • Expand mode — Shows the full access tree (union/intersection/leaf nodes) as a visual tree
  • History — Recent checks are displayed for quick re-testing

Role Management UI

Visual matrix grid for managing role assignments. Select users from a Kratos-integrated user picker and assign them to roles with a single click.

Dashboard: Authorization > Roles tab

Features:

  • User picker with search (connected to Kratos identity list)
  • Role assignment modal with namespace and relation selection
  • Visual matrix showing all users and their roles per namespace/object
  • Bulk assign/unassign via checkbox selection

Namespace Browser

List and explore all namespaces in your permission model. Each namespace shows its relations and the OPL definition.

Dashboard: Authorization > Namespaces tab

The browser displays:

  • Namespace name
  • Defined relations
  • OPL reference (if available)
  • Tuple count per namespace

Relationship Graph

Interactive ReactFlow visualization of the relationship graph. Nodes represent subjects and objects, edges represent relations. Drag nodes to explore the permission topology.

Dashboard: Authorization > Permissions tab > Graph view

The graph shows:

  • Subject nodes (users, groups) connected to object nodes (resources)
  • Edge labels showing the relation type
  • Color-coded edges for different relation types
  • Zoom, pan, and drag interactions

OPL Editor

Full-featured Monaco editor for writing Ory Permission Language (OPL) definitions. Includes TypeScript syntax highlighting and Keto API validation.

Dashboard: Authorization > Namespaces tab > Edit OPL

The editor provides:

  • Monaco editor with TypeScript syntax highlighting
  • Real-time syntax validation
  • Save to Keto API
  • Version history integration (save snapshots, restore previous versions)

Example OPL:

class User implements Namespace {}
class Organization implements Namespace {
related: {
admins: User[]
members: User[]
}
permits = {
manage: (ctx: Context) => this.related.admins.includes(ctx.subject),
view: (ctx: Context) =>
this.permits.manage(ctx) ||
this.related.members.includes(ctx.subject),
}
}
class Project implements Namespace {
related: {
owners: User[]
editors: User[]
viewers: (User | Organization["members"])[]
parent: Organization[]
}
permits = {
delete: (ctx: Context) => this.related.owners.includes(ctx.subject),
edit: (ctx: Context) =>
this.permits.delete(ctx) ||
this.related.editors.includes(ctx.subject),
view: (ctx: Context) =>
this.permits.edit(ctx) ||
this.related.viewers.includes(ctx.subject) ||
this.related.parent.traverse((org) => org.permits.view(ctx)),
}
}

OPL Version History

Save snapshots of your OPL definitions, restore previous versions, and view line-level diffs between versions.

Dashboard: Authorization > Namespaces tab > Version History

API Endpoints:

MethodPathDescription
GET/api/keto/opl-versionsList saved OPL snapshots
POST/api/keto/opl-versionsSave a new OPL snapshot
// List OPL versions
const versions = await fetch(
`${TRUSS_URL}/api/keto/opl-versions?name=default&limit=50`,
{ credentials: "include" }
).then(r => r.json());
// { versions: [{ id, name, content, created_by, created_at }], total: 12 }
// Save a new version
await fetch(`${TRUSS_URL}/api/keto/opl-versions`, {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "default",
content: "class User implements Namespace {}\n\nclass Project implements Namespace {\n ...\n}",
}),
});

Query parameters for listing versions:

ParameterDescription
nameOPL definition name (default: “default”)
limitMax versions to return (default: 50, max: 200)
offsetPagination offset

Model Templates

Pre-built OPL patterns you can load into the editor as a starting point. Available templates:

TemplateDescription
RBACClassic role hierarchy (admin > editor > viewer) with organization and project namespaces
Multi-TenantOrganization-scoped resources with team-level access, nested departments
Google Docs SharingDocument sharing model with owner, writer, commenter, reader roles and link sharing

Dashboard: Authorization > Namespaces tab > Templates dropdown

Each template can be loaded into the OPL editor, customized, and saved.

Permission Check Audit Trail

Every permission check performed through the API is logged to the audit trail. This provides a complete record of who checked what permission and the result.

Dashboard: Authorization > Audit section (within the main audit log, filtered by keto.permission.check action)

Each audit entry includes:

  • Timestamp
  • Actor (tenant ID)
  • Resource (namespace:object#relation)
  • Metadata (subject ID, subject set, allowed result)
Terminal window
# Query permission check audit logs via client API
curl "http://localhost:8787/v1/audit-logs?action=keto.permission.check&limit=50" \
-H "apikey: truss_sk_your_key"

Tuple creation and deletion are also logged:

  • keto.tuple.create — logged when a tuple is created
  • keto.tuple.delete — logged when a tuple is deleted

Batch Check API

Check up to 50 permissions in a single API call. Returns the result for each check. This is significantly more efficient than making individual check requests when you need to evaluate multiple permissions at once (e.g., rendering a UI with conditional access controls).

API Endpoint:

MethodPathDescription
POST/api/keto/batch-checkCheck up to 50 permissions in one call
const results = await fetch(`${TRUSS_URL}/api/keto/batch-check`, {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
checks: [
{ namespace: "Project", object: "proj-1", relation: "view", subject_id: "user-123" },
{ namespace: "Project", object: "proj-1", relation: "edit", subject_id: "user-123" },
{ namespace: "Project", object: "proj-1", relation: "delete", subject_id: "user-123" },
{ namespace: "Project", object: "proj-2", relation: "view", subject_id: "user-123" },
{ namespace: "Document", object: "doc-1", relation: "view", subject_id: "user-123" },
],
}),
}).then(r => r.json());
// {
// "results": [
// { "namespace": "Project", "object": "proj-1", "relation": "view", "subject_id": "user-123", "allowed": true },
// { "namespace": "Project", "object": "proj-1", "relation": "edit", "subject_id": "user-123", "allowed": true },
// { "namespace": "Project", "object": "proj-1", "relation": "delete", "subject_id": "user-123", "allowed": false },
// { "namespace": "Project", "object": "proj-2", "relation": "view", "subject_id": "user-123", "allowed": true },
// { "namespace": "Document", "object": "doc-1", "relation": "view", "subject_id": "user-123", "allowed": false }
// ]
// }

Limits: Maximum 50 checks per batch request. Returns 400 if the limit is exceeded.


Health Check

Verify that Keto is reachable and operational. The health endpoint is cached for 30 seconds.

API Endpoint:

MethodPathDescription
GET/api/keto/healthCheck Keto read API health
Terminal window
curl http://localhost:8787/api/keto/health
# { "read": { "status": "ok" }, "writeConfigured": true }

Complete API Reference

MethodPathDescription
GET/api/keto/healthHealth check (cached 30s)
GET/api/keto/namespacesList namespaces (tenant-scoped)
GET/api/keto/relation-tuplesList tuples with filters
PUT/api/keto/relation-tuplesCreate a tuple
DELETE/api/keto/relation-tuplesDelete a tuple
POST/api/keto/checkCheck a permission
POST/api/keto/batch-checkBatch check (up to 50)
GET/api/keto/expandExpand permission tree
POST/api/keto/who-can-accessReverse lookup: who has access?
GET/api/keto/subject-tuples/:idAll tuples for a subject
POST/api/keto/relation-tuples/importBulk import (up to 500)
POST/api/keto/relation-tuples/batch-deleteBulk delete
GET/api/keto/opl-versionsList OPL snapshots
POST/api/keto/opl-versionsSave OPL snapshot