Permissions & Authorization
How authentication, authorization, and permissions work in Arke -- JWT auth, API keys (user vs agent), collections, roles, temporal permissions, and the permission resolution flow.
Authentication
Arke supports three authentication methods, each suited to different use cases.
JWT (JSON Web Tokens)
Users authenticate through Supabase for web sessions. The gateway validates the JWT and extracts user identity.
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...JWT auth provides access to user-specific claims (email, name, Supabase ID) and is typically used for browser-based applications.
User API Keys (uk_)
User API keys provide programmatic access for SDKs, CLIs, and CI/CD pipelines. They authenticate as the user with the same permissions.
Authorization: ApiKey uk_a1b2c3d4e5f6...| Property | Value |
|---|---|
| Prefix | uk_ |
| Default expiry | 90 days |
| Maximum expiry | 365 days |
| Use cases | SDK, CLI, automation, CI/CD |
User keys are managed via /auth/api-keys endpoints.
Agent API Keys (ak_)
Agent API keys authenticate external services (agents) that process entities on behalf of users. They authenticate as the agent, not the user.
Authorization: ApiKey ak_x9y8z7w6v5u4...| Property | Value |
|---|---|
| Prefix | ak_ |
| Default expiry | 365 days |
| Use cases | External processing services, webhooks |
| Owner tracking | Each agent has an owner_pi (the user who controls it) |
Agent keys are managed via /agents/{id}/api-keys endpoints.
Key Differences: User Keys vs Agent Keys
| Aspect | User Key (uk_) | Agent Key (ak_) |
|---|---|---|
| Authenticates as | User entity | Agent entity |
| Permissions | User's own permissions | Permissions granted to the agent |
| Actor type | user | agent |
| Owner field | N/A | ownerPi (controlling user) |
| Audit trail | Actions logged as user | Actions logged as agent + owner |
Permission Resolution
When a request is made, Arke determines permissions through a specific resolution order.
Resolution Flow
1. Self-ownership check (users can view/update themselves)
↓
2. Parent collection lookup (find entity's collection)
↓
3. Role resolution (find actor's role in collection)
↓
4. Action check (does role allow the action?)
↓
5. Open season fallback (no collection = public access)Resolution Methods
| Method | When It Applies | Result |
|---|---|---|
self | User accessing their own user entity | Grants user:view, user:update |
collection | Entity belongs to a collection | Permissions based on actor's role |
open_season | Entity has no parent collection | Public read access (with restrictions) |
Special Rules
User updates require explicit permission. Unlike other entities, user:update is never granted via open season. Users can only be modified by:
- The user themselves (self-ownership)
- Someone with explicit permission in a collection containing the user
Deleted collections hide their contents. When a collection is soft-deleted, all entities inside become inaccessible. The only exception: the user who deleted the collection can restore it via collection:restore.
Collections as Permission Boundaries
Every entity can belong to a collection. Collections define roles (named permission sets) and assign those roles to users.
How Collections Work
- A collection defines roles with specific allowed actions
- Users are assigned roles via relationships on the collection
- When accessing an entity, Arke finds its parent collection
- Arke looks up the actor's role in that collection
- The role's actions determine what's allowed
Default Roles
New collections include these roles by default:
| Role | Actions | Purpose |
|---|---|---|
owner | *:view, *:update, *:create, collection:update, collection:manage | Full control including settings |
editor | *:view, *:update, *:create | Modify entities, no settings access |
viewer | *:view | Read-only access |
public | *:view | Wildcard assignment for anonymous users |
Role Resolution Priority
When determining an actor's role:
- Direct assignment (highest priority): Actor is explicitly assigned a role
- Group membership (future): Actor belongs to a group with a role
- Wildcard assignment (lowest priority): Role assigned to
*(everyone)
collection.relationships:
- predicate: "owner", peer: "01JUSER123...", peer_type: "user" → Direct
- predicate: "viewer", peer: "*", peer_type: "wildcard" → WildcardPermission Actions
Actions follow the resource:verb pattern. This creates a structured permission model.
Action Format
<resource>:<verb>Examples:
entity:view-- View any entityfile:update-- Update file entitiescollection:manage-- Manage collection membership
Registered Actions
| Action | Description |
|---|---|
entity:view | Read entity content |
entity:update | Modify existing entity |
entity:create | Create new entities |
entity:delete | Soft-delete entity |
file:view | View file metadata |
file:update | Update file content |
file:download | Access original file binary |
collection:view | View collection |
collection:update | Modify collection settings |
collection:manage | Manage roles and membership |
collection:delete | Delete collection |
collection:restore | Restore deleted collection |
user:view | View user profile |
user:update | Modify user profile |
Wildcards
Wildcards allow granting broad permissions efficiently.
Verb wildcards match any resource type:
*:view → Matches entity:view, file:view, user:view, etc.
*:update → Matches entity:update, file:update, user:update, etc.Type wildcards match any verb for a type:
file:* → Matches file:view, file:update, file:download
entity:* → Matches entity:view, entity:update, entity:create, entity:deleteType Implications
Entity types form a hierarchy. Granting a parent type implies child types:
entity:view → implies file:view, user:view, collection:view, etc.
entity:* → implies file:*, user:*, etc.This means *:view on the owner role grants view access to all entity types.
Verb Implications
Some verbs imply others:
viewimpliesdownload(if you can view a file, you can download it)
Action Mapping for Collections
When checking permissions on a collection itself, entity actions map to collection actions:
| Request | Effective Check |
|---|---|
entity:view on collection | collection:view |
entity:update on collection | collection:update |
entity:delete on collection | collection:delete |
Note: entity:create is NOT mapped -- it means "create entities inside this collection."
Temporal Permissions
Role assignments can include expiration times for automatic revocation.
How It Works
Role assignments are stored as relationships with optional temporal properties:
{
"predicate": "editor",
"peer": "01JUSER789...",
"peer_type": "user",
"properties": {
"expires_at": "2025-06-01T00:00:00.000Z",
"granted_at": "2025-01-01T12:00:00.000Z",
"granted_by": "01JUSER123..."
}
}Temporal Properties
| Property | Description |
|---|---|
expires_at | ISO 8601 timestamp when access expires (omit for permanent) |
granted_at | When the role was assigned |
granted_by | Who assigned the role |
Behavior
- Expired relationships are skipped during role resolution
- Invalid date formats are treated as permanent (to avoid lockouts)
- No background job required -- expiry is checked at query time
Agent Authorization
Agents (external processing services) have a specialized permission model.
How Agents Work
- Agents are entities of type
agentwith API keys - Each agent has an owner (the user who controls it)
- Agents declare what permissions they need (
actions_required) - Users grant agents access to specific collections
Agent Permission Flow
Agent authenticates (ak_ key)
↓
Request includes target entity
↓
Find entity's collection
↓
Check if agent has role in collection
↓
Verify role allows requested actionRequest Signing
When Arke invokes agents, it signs the request with Ed25519:
X-Arke-Signature: <signature>
X-Arke-Timestamp: <unix-timestamp>Agents should verify this signature to ensure requests originate from Arke.
Audit Trail
Agent actions are logged with both identities:
edited_by.agent: The agent that performed the actionedited_by.agent_owner: The user who owns the agent
Permission API
You can query permissions programmatically.
Get Permission System Metadata
GET /permissionsReturns all registered actions, verbs, types, implications, and default roles. This endpoint requires no authentication and is useful for building dynamic role editors or validating actions client-side.
{
"actions": ["collection:delete", "collection:manage", "collection:restore", "collection:update", "collection:view", "entity:create", "entity:delete", "entity:update", "entity:view", "file:download", "file:reupload", "file:update", "file:view", "user:update", "user:view"],
"verbs": ["create", "delete", "download", "manage", "restore", "reupload", "update", "view"],
"types": ["collection", "entity", "file", "user"],
"implications": {
"view": ["download"]
},
"type_hierarchy": {
"base_type": "entity",
"description": "Actions with type 'entity' imply the same action for all specific types.",
"restrictions": [...]
},
"wildcards": {
"verb": { "pattern": "*:{verb}", "example": "*:view", "description": "..." },
"type": { "pattern": "{type}:*", "example": "file:*", "description": "..." }
},
"restrictions": [
"collection:* is not allowed - use explicit collection actions for security",
"*:update does not match collection:update - collection operations require explicit permission",
...
],
"default_roles": {
"owner": ["*:view", "*:update", "*:create", "collection:update", "collection:manage"],
"editor": ["*:view", "*:update", "*:create"],
"viewer": ["*:view"],
"public": ["*:view"]
}
}Check Entity Permissions
GET /entities/{id}/permissionsReturns:
{
"entity_id": "01JFILE123...",
"entity_type": "file",
"allowed_actions": ["entity:view", "file:view", "file:download"],
"resolution": {
"method": "collection",
"collection_id": "01JCOLL456...",
"role": "viewer"
}
}Resolution Methods in Response
| Method | Meaning |
|---|---|
collection | Permissions came from a role in a collection |
self | User accessing their own entity |
open_season | Entity has no collection (public access) |
If resolution.deleted is true, the collection was soft-deleted and the entity is inaccessible.