RBAC Reference¶
Tilde uses a simple policy DSL for fine-grained access control. Every API action is authorized against the principal's effective policies, which are resolved through direct attachments and group memberships. Agents are authorized differently: their only grant source is an inline policy, intersected with the creator's effective policy (see Agents).
Concepts¶
Groups¶
Groups organize users and can be nested (a group can contain other groups). They are scoped to an organization.
- Members can be users, other groups, or roles (
subject_type:"user","group", or"role") - Circular nesting is prevented automatically
- Group memberships are resolved recursively when evaluating policies
Roles¶
Roles are non-human identities scoped to an organization. They are designed for CI/CD pipelines, automation scripts, and service-to-service integrations.
- Roles authenticate exclusively via API keys — they have no username, email, or password
- Roles can be added to groups, have policies attached, and hold API keys
- When a role performs an action, audit logs record
principal_type: "role"to distinguish role activity from user activity - Like users, a role's effective policies include direct attachments and group memberships (resolved recursively)
Agents¶
Agents are non-human identities designed for AI/ML pipelines, automated data processing, and LLM-powered workflows. They authenticate via API keys.
Agents are authorized by a different rule than users and roles. Two guarantees drive the model:
- An agent can never do more than its creator. Whatever the creator's effective policy denies or does not allow, the agent cannot do either.
- An agent defaults to nothing. It has no permissions at all unless you write an inline policy on it.
The only source of grants on an agent is its inline policy — a policy written directly on the agent record. When an agent attempts an action, the authorizer evaluates:
- the creator's effective policy (direct attachments + group-inherited policies), and
- the agent's inline policy.
The action is allowed only if both buckets allow it (a deny in either bucket denies overall). This is an intersection, not a union:
- If the creator can do
GetRepository()on every repo and the inline policy saysGetRepository(repository:"foo"), the agent can read onlyfoo. - If the creator is limited to
GetRepository(repository:"foo")and the inline policy saysGetRepository(), the agent is still limited tofoo— the creator is the ceiling. - If the inline policy is empty, the agent is denied on every action regardless of what the creator can do.
Direct policy attachments and group memberships on agents do not grant any permissions. They are ignored during agent authorization; inline is the only positive source.
?Action() (require approval) rules in the inline policy still work the same way: the action proceeds if the creator also allows it, and the resulting session is tainted so a human must approve before committing.
When an agent performs an action, audit logs record principal_type: "agent".
Running sandboxes as agents¶
Before the intersection model, sandboxes bypassed per-action authorization for session create and commit — the sandbox lifecycle acted as a blessed write path. That's no longer true. Two distinct policy checks apply:
Caller side: UseAgent. Setting run_as: { type: "agent", ... } on CreateSandbox, or wiring run_as into a sandbox trigger, requires the caller to have UseAgent on the target agent. This is also re-checked when a trigger fires (so revoking UseAgent from the trigger's creator immediately stops the trigger from spawning new sandboxes). Without this grant, the request returns 403 FORBIDDEN. The built-in Owner, SuperUser, and AgentManager policies include UseAgent; other principals must be granted it explicitly. The role analogue is UseRole — see Impersonation.
Target side: agent inline policy. The agent's inline policy must allow all three of:
CreateSandboxon the repositoryCreateSessionon the repositoryCommitSessionon the repository (re-checked inside the orchestrator just before it commits, so mid-run inline-policy changes take effect)
If any is missing, the sandbox create request returns 403 FORBIDDEN identifying the missing action. A denied re-check at commit time rolls the session back and marks the sandbox as failed with reason policy_violation.
Agents created through the Tilde web UI start with an inline policy that includes these lifecycle actions plus baseline reads; CLI or API callers that pass an explicit inline_policy are responsible for including them.
Policies¶
Policies are rules written in Tilde's simple policy DSL that define allow/deny/approval rules. Each policy belongs to an organization.
- Built-in policies are seeded when an organization is created and cannot be modified or deleted
- Custom policies can be created, updated, validated, and deleted
- Policies are evaluated with an input object describing the action, principal, organization, and resource
Tilde supports two policy types:
- RBAC policies — control API actions (described on this page)
- Network policies — control sandbox HTTP/HTTPS traffic (see Network Isolation)
Attachments¶
Policies are attached to principals (users, groups, or roles). A user's or role's effective policies include:
- Policies attached directly to the principal
- Policies attached to any group the principal belongs to (recursively)
Attachments to agent principals are ignored at evaluation time. Agents are authorized from their inline policy plus the creator's effective policy — see Agents.
Authorization Logic¶
For users and roles, Tilde uses deny-overrides-allow semantics:
- All effective policies are evaluated
- If any rule denies the action (
!Action()), the action is denied - Otherwise, if at least one rule allows the action (
Action()), the action is allowed - If no rule allows the action, it is denied (default deny)
For agents, the rule is an intersection of two buckets:
- Evaluate the creator's effective policy (direct + group-inherited) using deny-overrides-allow
- Evaluate the inline policy on the agent itself
- The action is allowed only if both buckets allow it. A deny in either bucket denies overall. An empty inline policy denies everything.
Approval Requirement¶
Policies can define a require-approval rule (?Action()) in addition to allow and deny. This only applies to agents and only from the agent's inline policy:
- After allow/deny is resolved, the inline policy is checked for
?Action()rules - If the inline policy requires approval for the action (and the creator allows it), the write proceeds but the session is tainted
- A tainted session cannot be committed by the agent — it requires a human user to approve and commit the changes via
ApproveSessionChanges - If the agent attempts to commit a tainted session, the API returns
202 Acceptedwith a message indicating approval is required - The
?(require approval) prefix is only meaningful for agent principals, and only when written in the agent's inline policy. User and role actions are never subject to approval;?rules in policies attached to users, roles, or groups have no effect for agents either.
Built-in Policies¶
When an organization is created, six built-in policies are seeded:
Owner¶
Grants full access to all actions and resources in the organization. Automatically attached to the organization creator.
ListRepositories()
CreateRepository()
DeleteRepository()
GetRepository()
ListObjects()
GetObject()
DeleteObject()
PutObject()
CreateSession()
CommitSession()
RollbackSession()
ApproveSessionChanges()
ListMembers()
AddMember()
RemoveMember()
AddGroup()
ListGroups()
AddToGroup()
RemoveFromGroup()
AttachPolicy()
DetachPolicy()
AddConnector()
RemoveConnector()
AttachConnector()
DetachConnector()
LogCommits()
RevertCommit()
CreateRole()
ListRoles()
GetRole()
DeleteRole()
CreateRoleKey()
ListRoleKeys()
RevokeRoleKey()
CreateAgent()
ListAgents()
GetAgent()
DeleteAgent()
UpdateAgent()
CreateAgentKey()
ListAgentKeys()
RevokeAgentKey()
ManageAgentSecrets()
ReadAgentSecrets()
IssueSessionToken()
ManageRepositorySecrets()
ReadRepositorySecrets()
CreateInvitation()
ListInvitations()
RevokeInvitation()
CreateSandbox()
ListSandboxes()
GetSandbox()
CancelSandbox()
CreateSandboxTrigger()
ListSandboxTriggers()
GetSandboxTrigger()
UpdateSandboxTrigger()
DeleteSandboxTrigger()
ListSandboxTriggerRuns()
UseAgent()
UseRole()
ReadAll¶
Read-only access to repositories, objects, members, and groups.
ListRepositories()
GetRepository()
ListObjects()
GetObject()
LogCommits()
ListMembers()
ListGroups()
SuperUser¶
All operations except admin actions (member/group/policy management, repository creation/deletion).
ListRepositories()
GetRepository()
ListObjects()
GetObject()
DeleteObject()
PutObject()
CreateSession()
CommitSession()
RollbackSession()
ApproveSessionChanges()
ListMembers()
ListGroups()
AddConnector()
RemoveConnector()
AttachConnector()
DetachConnector()
LogCommits()
RevertCommit()
CreateRole()
ListRoles()
GetRole()
DeleteRole()
CreateRoleKey()
ListRoleKeys()
RevokeRoleKey()
CreateAgent()
ListAgents()
GetAgent()
DeleteAgent()
UpdateAgent()
CreateAgentKey()
ListAgentKeys()
RevokeAgentKey()
ManageAgentSecrets()
ReadAgentSecrets()
IssueSessionToken()
ManageRepositorySecrets()
ReadRepositorySecrets()
CreateSandbox()
ListSandboxes()
GetSandbox()
CancelSandbox()
CreateSandboxTrigger()
ListSandboxTriggers()
GetSandboxTrigger()
UpdateSandboxTrigger()
DeleteSandboxTrigger()
ListSandboxTriggerRuns()
UseAgent()
UseRole()
AgentManager¶
Allows a user or role to create agents and manage the ones they created. Mutating actions (update, delete, key management, secrets) are scoped via created_by:$principal.id so that users can only manage agents they own. Holders can also list any sandbox in the organization but can only view (details/status/logs/network log/terminal) and cancel sandboxes that were created by agents they own — see the agent_created_by modifier under Sandbox Operations.
CreateAgent()
ListAgents()
GetAgent()
UpdateAgent(created_by:$principal.id)
DeleteAgent(created_by:$principal.id)
CreateAgentKey(created_by:$principal.id)
ListAgentKeys(created_by:$principal.id)
RevokeAgentKey(created_by:$principal.id)
ManageAgentSecrets(created_by:$principal.id)
ReadAgentSecrets(created_by:$principal.id)
ListSandboxes()
GetSandbox(agent_created_by:$principal.id)
CancelSandbox(agent_created_by:$principal.id)
ListSandboxTriggers()
GetSandboxTrigger(agent_created_by:$principal.id)
UpdateSandboxTrigger(agent_created_by:$principal.id)
DeleteSandboxTrigger(agent_created_by:$principal.id)
ListSandboxTriggerRuns(agent_created_by:$principal.id)
UseAgent(created_by:$principal.id)
UseAgent(created_by:$principal.id) lets the holder set run_as on sandboxes and triggers to any agent they created — but not to agents owned by someone else. Drop the modifier (just UseAgent()) if the holder should be able to impersonate any agent in the org.
Note
This policy is designed for users and roles. Attaching it to an agent has no effect — agents draw their permissions from their inline policy only, bounded by the creator's effective policy.
SandboxManager¶
Allows a user or role to run sandboxes and manage sandbox triggers in any repository in the organization. The principal can create and list sandboxes and triggers freely, but can only view (details/status/logs/network log/terminal) or cancel sandboxes — and view, update, or delete triggers — that they created themselves.
CreateSandbox()
ListSandboxes()
GetSandbox(created_by:$principal.id)
CancelSandbox(created_by:$principal.id)
CreateSandboxTrigger()
ListSandboxTriggers()
GetSandboxTrigger(created_by:$principal.id)
UpdateSandboxTrigger(created_by:$principal.id)
DeleteSandboxTrigger(created_by:$principal.id)
ListSandboxTriggerRuns(created_by:$principal.id)
This is the right starting point for an interactive user who needs to run their own sandboxes and triggers but should not be able to inspect, modify, or delete those belonging to other principals (including agents). Combine with ReadAll if they also need read access to repository data.
SandboxManager deliberately omits UseAgent and UseRole — holders can only run sandboxes as themselves. Add UseAgent(...) or UseRole(...) if a sandbox author also needs to delegate execution to a specific agent or role.
DefaultNetworkAccess¶
Default network access policy for sandboxes. Allows common language and OS package managers (Debian/Ubuntu apt, Alpine apk), AI APIs, and GitHub. All other destinations are denied by default.
# Language package managers
HttpRequest(host:"registry.npmjs.org")
HttpRequest(host:"registry.yarnpkg.com")
HttpRequest(host:"pypi.org")
HttpRequest(host:"files.pythonhosted.org")
HttpRequest(host:"*.crates.io")
HttpRequest(host:"rubygems.org")
HttpRequest(host:"proxy.golang.org")
HttpRequest(host:"sum.golang.org")
# OS package managers — Debian / Ubuntu (apt)
HttpRequest(host:"deb.debian.org")
HttpRequest(host:"security.debian.org")
HttpRequest(host:"archive.ubuntu.com")
HttpRequest(host:"*.archive.ubuntu.com")
HttpRequest(host:"security.ubuntu.com")
HttpRequest(host:"ports.ubuntu.com")
HttpRequest(host:"changelogs.ubuntu.com")
HttpRequest(host:"keyserver.ubuntu.com")
# OS package managers — Alpine (apk)
HttpRequest(host:"dl-cdn.alpinelinux.org")
HttpRequest(host:"*.alpinelinux.org")
# AI APIs
HttpRequest(host:"api.openai.com")
HttpRequest(host:"api.anthropic.com")
# GitHub
HttpRequest(host:"github.com")
HttpRequest(host:"raw.githubusercontent.com")
HttpRequest(host:"objects.githubusercontent.com")
Warning
Built-in policies cannot be modified or deleted. The last Owner policy cannot be detached from all principals — at least one user or group must always have the Owner policy.
Policy DSL Syntax¶
Policies are written in Tilde's simple policy DSL. Each line is a single rule that allows, denies, or requires approval for an action, optionally constrained by modifiers.
Grammar¶
Action() # allow the action
!Action() # deny the action
?Action() # require approval for the action
Action(modifier:"value") # allow with a modifier constraint
!Action(modifier:"value") # deny with a modifier constraint
?Action(modifier:"value") # require approval with a modifier constraint
- No prefix = allow
!prefix = deny?prefix = require approval (agents only)- Multiple modifiers can be combined:
Action(modifier1:"value1", modifier2:"value2") - One rule per line. Blank lines and lines starting with
#are ignored.
Pattern Matching¶
Modifier values support fnmatch-style glob patterns:
| Pattern | Matches |
|---|---|
* |
Any string |
? |
Any single character |
[abc] |
Any character in the set |
[!abc] |
Any character not in the set |
[*] |
A literal * character |
Examples:
PutObject(repository:"my-data", path:"outputs/*") # allow writes under outputs/ in my-data
GetObject(path:"*.csv") # allow reading any CSV file
HttpRequest(host:"*.example.com") # allow requests to all example.com subdomains
Interpolation Variables¶
Modifier values can reference properties of the current principal using $principal variables:
| Variable | Description |
|---|---|
$principal.id |
UUID of the acting user, role, or agent |
$principal.name |
Name of the acting principal |
$principal.type |
Principal type: "user", "role", or "agent" |
Example:
PutObject(path:"users/$principal.name/*") # each user can write only under their own path
DeleteAgent(created_by:$principal.id) # users can only delete agents they created
Action Reference¶
Each action lists the modifiers it accepts and whether it supports the ? (require approval) prefix. Omitted modifiers match any value.
The ? prefix is only meaningful for agent principals. When an agent performs an action marked ?, the session becomes tainted and requires a human to approve and commit the changes. Actions marked with Approval: yes are the actions where the system checks for ? rules.
Repository Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
ListRepositories |
List repositories in the organization | repository, organization |
no |
CreateRepository |
Create a new repository | repository, organization |
no |
DeleteRepository |
Delete a repository | repository, organization |
no |
GetRepository |
View repository details | repository, organization |
no |
Object Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
ListObjects |
List objects in a repository | repository, path, organization |
no |
GetObject |
Read an object | repository, path, organization |
no |
PutObject |
Write an object | repository, path, organization |
yes |
DeleteObject |
Delete an object | repository, path, organization |
yes |
The path modifier supports fnmatch patterns: PutObject(path:"data/*") allows writing any object under data/.
ListObjects also gates the catalog metadata endpoints used by external query engines that enumerate every object's metadata for a snapshot. Granting ListObjects is sufficient for both the user-facing list endpoint and these lower-level metadata reads.
Session Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
CreateSession |
Create an editing session | repository, session, organization |
no |
CommitSession |
Commit session changes | repository, session, organization |
no |
RollbackSession |
Rollback a session | repository, session, organization |
no |
ApproveSessionChanges |
Approve pending session changes | repository, session, organization |
no |
Commit Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
LogCommits |
View commit history | repository, organization |
no |
RevertCommit |
Revert a commit | repository, organization |
no |
Member Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
ListMembers |
List organization members | member, organization |
no |
RemoveMember |
Remove a member from the organization | member, organization |
no |
Members are added via the invitation flow (see Invitation Operations below). The AddMember action is retained for backward compatibility but is no longer reachable through any public endpoint.
Invitation Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
CreateInvitation |
Invite a user to the organization (by email or username) | organization |
no |
ListInvitations |
List pending organization invitations | organization |
no |
RevokeInvitation |
Revoke a pending invitation | organization |
no |
Group Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
AddGroup |
Create a new group | group, organization |
no |
ListGroups |
List groups | group, organization |
no |
AddToGroup |
Add a member to a group | group, organization |
no |
RemoveFromGroup |
Remove a member from a group | group, organization |
no |
Policy Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
AttachPolicy |
Attach a policy to a principal | organization |
no |
DetachPolicy |
Detach a policy from a principal | organization |
no |
Connector Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
AddConnector |
Add an external connector | connector, repository, organization |
no |
RemoveConnector |
Remove a connector | connector, repository, organization |
no |
AttachConnector |
Attach a connector to a repository | connector, repository, organization |
no |
DetachConnector |
Detach a connector from a repository | connector, repository, organization |
no |
Role Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
CreateRole |
Create a role | role, organization |
no |
ListRoles |
List roles | role, organization |
no |
GetRole |
View role details | role, organization |
no |
DeleteRole |
Delete a role | role, organization |
no |
CreateRoleKey |
Create an API key for a role | role, organization |
no |
ListRoleKeys |
List API keys for a role | role, organization |
no |
RevokeRoleKey |
Revoke an API key for a role | role, organization |
no |
Agent Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
CreateAgent |
Create an agent | agent, created_by, organization |
no |
ListAgents |
List agents | agent, created_by, organization |
no |
GetAgent |
View agent details | agent, created_by, organization |
no |
DeleteAgent |
Delete an agent | agent, created_by, organization |
no |
UpdateAgent |
Update agent settings | agent, created_by, organization |
no |
CreateAgentKey |
Create an API key for an agent | agent, created_by, organization |
no |
ListAgentKeys |
List API keys for an agent | agent, created_by, organization |
no |
RevokeAgentKey |
Revoke an API key for an agent | agent, created_by, organization |
no |
The created_by modifier is commonly used with $principal.id to scope actions to agents owned by the current user: DeleteAgent(created_by:$principal.id).
Secret Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
ManageRepositorySecrets |
Create or delete repository secrets | repository, secret_key, organization |
no |
ReadRepositorySecrets |
Read repository secret values | repository, secret_key, organization |
no |
ManageAgentSecrets |
Create or delete agent secrets | agent, created_by, secret_key, organization |
no |
ReadAgentSecrets |
Read agent secret values | agent, created_by, secret_key, organization |
no |
Other¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
IssueSessionToken |
Issue a scoped session token | organization |
no |
Sandbox Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
CreateSandbox |
Create and run a sandbox in a repository | repository, organization |
no |
ListSandboxes |
List sandboxes in a repository | repository, organization |
no |
GetSandbox |
View a sandbox: details, status, stdout/stderr log, network log, interactive terminal | repository, sandbox, created_by, created_by_type, agent_created_by, organization |
no |
CancelSandbox |
Cancel a running sandbox | repository, sandbox, created_by, created_by_type, agent_created_by, organization |
no |
A single GetSandbox permission gates all read-side sandbox endpoints — there is no separate action for stdout, the network log, or the interactive terminal. Granting GetSandbox lets the principal see everything the sandbox produced.
The created_by, created_by_type, and agent_created_by modifiers let you scope GetSandbox and CancelSandbox by ownership:
| Modifier | What it matches |
|---|---|
created_by:$principal.id |
Sandboxes the principal created directly (does not match sandboxes created by their agents) |
created_by_type:"agent" |
Sandboxes whose creator is an agent (regardless of which one) |
agent_created_by:$principal.id |
Sandboxes created by an agent whose own creator is the principal — used by AgentManager to manage sandboxes belonging to their agents |
Examples:
# Power user: run anything, but only inspect/cancel your own
CreateSandbox()
ListSandboxes()
GetSandbox(created_by:$principal.id)
CancelSandbox(created_by:$principal.id)
# Manage sandboxes started by agents you own
GetSandbox(agent_created_by:$principal.id)
CancelSandbox(agent_created_by:$principal.id)
# Read-only on every sandbox in a specific repository
GetSandbox(repository:"my-data")
ListSandboxes(repository:"my-data")
Sandbox Trigger Operations¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
CreateSandboxTrigger |
Create a sandbox trigger in a repository | repository, organization |
no |
ListSandboxTriggers |
List sandbox triggers in a repository | repository, organization |
no |
GetSandboxTrigger |
View a sandbox trigger's configuration | repository, trigger, created_by, agent_created_by, organization |
no |
UpdateSandboxTrigger |
Update a trigger (covers PUT) or toggle it (PATCH) | repository, trigger, created_by, agent_created_by, organization |
no |
DeleteSandboxTrigger |
Delete a sandbox trigger | repository, trigger, created_by, agent_created_by, organization |
no |
ListSandboxTriggerRuns |
View the run history of a sandbox trigger | repository, trigger, created_by, agent_created_by, organization |
no |
A single UpdateSandboxTrigger permission gates both the full PUT update and the PATCH (enable/disable) toggle — there is no separate action for toggling.
The created_by and agent_created_by modifiers let you scope per-trigger actions by ownership:
| Modifier | What it matches |
|---|---|
created_by:$principal.id |
Triggers the principal created themselves |
agent_created_by:$principal.id |
Triggers configured to run_as an agent whose own creator is the principal — used by AgentManager to manage triggers that fire as their agents |
Examples:
# Power user: define triggers, but only manage your own
CreateSandboxTrigger()
ListSandboxTriggers()
GetSandboxTrigger(created_by:$principal.id)
UpdateSandboxTrigger(created_by:$principal.id)
DeleteSandboxTrigger(created_by:$principal.id)
ListSandboxTriggerRuns(created_by:$principal.id)
# Manage triggers that fire as agents you own
GetSandboxTrigger(agent_created_by:$principal.id)
UpdateSandboxTrigger(agent_created_by:$principal.id)
DeleteSandboxTrigger(agent_created_by:$principal.id)
Impersonation¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
UseAgent |
Run sandboxes or triggers under a given agent's identity | agent, created_by, organization |
no |
UseRole |
Run sandboxes or triggers under a given role's identity | role, organization |
no |
UseAgent / UseRole gate the run_as field on CreateSandbox and on sandbox triggers. A caller without these grants can only run sandboxes as themselves -- regardless of what the target agent's or role's own policy permits. Combine with created_by:$principal.id to scope impersonation to agents you created.
# Run sandboxes as any agent you own
UseAgent(created_by:$principal.id)
# Run sandboxes as the "ci-bot" role specifically
UseRole(role:"ci-bot")
Network (Sandbox Proxy)¶
| Action | Description | Modifiers | Approval |
|---|---|---|---|
HttpRequest |
Make an outbound HTTP/HTTPS request from a sandbox | host, scheme, port, method, path |
no |
Network policies are evaluated by the proxy sidecar in sandbox containers. See Network Isolation for details.
HttpRequest modifiers:
| Modifier | Description | Example |
|---|---|---|
host |
Hostname (supports fnmatch) | HttpRequest(host:"*.github.com") |
scheme |
URL scheme | HttpRequest(scheme:"https") |
port |
Port number | HttpRequest(port:"443") |
method |
HTTP method | HttpRequest(method:"GET") |
path |
URL path (supports fnmatch) | HttpRequest(path:"/api/*") |
API Endpoints¶
Groups¶
Create Group¶
POST /api/v1/organizations/{organization}/groups
Response: 201 Created
{
"id": "group-uuid",
"organization_id": "org-uuid",
"name": "data-engineers",
"description": "Data engineering team",
"created_by": "user-uuid",
"created_at": "2025-01-15T10:30:00Z"
}
List Groups¶
GET /api/v1/organizations/{organization}/groups
| Parameter | Type | Required | Description |
|---|---|---|---|
after |
query | No | Pagination cursor |
amount |
query | No | Page size |
Get Group¶
GET /api/v1/organizations/{organization}/groups/{group_id}
Update Group¶
PUT /api/v1/organizations/{organization}/groups/{group_id}
Request body: { "name": "...", "description": "..." } (both optional)
Delete Group¶
DELETE /api/v1/organizations/{organization}/groups/{group_id}
Add Group Member¶
POST /api/v1/organizations/{organization}/groups/{group_id}/members
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
subject_type |
string | Yes | "user", "group", or "role" |
subject_id |
string (UUID) | Yes | User, group, or role ID to add |
Remove Group Member¶
DELETE /api/v1/organizations/{organization}/groups/{group_id}/members
| Parameter | Type | Required | Description |
|---|---|---|---|
subject_type |
query | Yes | "user", "group", or "role" |
subject_id |
query | Yes | User, group, or role UUID |
Roles¶
Create Role¶
POST /api/v1/organizations/{organization}/roles
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Role name (must be unique within the organization) |
description |
string | No | Description of the role's purpose |
Response: 201 Created
{
"id": "role-uuid",
"organization_id": "org-uuid",
"name": "ci-deployer",
"description": "CI/CD pipeline for production deployments",
"created_by": "user-uuid",
"created_at": "2025-01-15T10:30:00Z"
}
List Roles¶
GET /api/v1/organizations/{organization}/roles
| Parameter | Type | Required | Description |
|---|---|---|---|
after |
query | No | Pagination cursor |
amount |
query | No | Page size |
Get Role¶
GET /api/v1/organizations/{organization}/roles/{role_name}
Delete Role¶
DELETE /api/v1/organizations/{organization}/roles/{role_name}
Deleting a role revokes all of its API keys and removes it from all groups and policy attachments.
Create Role API Key¶
POST /api/v1/organizations/{organization}/roles/{role_name}/auth/keys
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | A human-readable name for the API key |
Response: 201 Created
{
"token_id": "key-id",
"token": "trk-key-id_secret",
"name": "github-actions-key",
"created_at": "2025-01-15T10:30:00Z"
}
Warning
The token field is only returned once at creation time. Store it securely — it cannot be retrieved again.
List Role API Keys¶
GET /api/v1/organizations/{organization}/roles/{role_name}/auth/keys
Response: 200 OK
{
"results": [
{
"token_id": "key-id",
"name": "github-actions-key",
"token_hint": "trk-...abc",
"created_at": "2025-01-15T10:30:00Z",
"last_used_at": "2025-01-16T08:00:00Z"
}
]
}
Revoke Role API Key¶
DELETE /api/v1/organizations/{organization}/roles/{role_name}/auth/keys/{key_id}
Agents¶
Agents are non-human identities that can authenticate via API keys and perform actions within an organization. Unlike roles, agents can carry metadata and have an update endpoint. Agents cannot create, update, or delete other agents — only users and roles can manage agents.
Create Agent¶
POST /api/v1/organizations/{organization}/agents
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Agent name (must be unique within the organization) |
description |
string | No | Description of the agent's purpose |
metadata |
object | No | Arbitrary key-value pairs (string keys and values) |
Response: 201 Created
{
"id": "agent-uuid",
"organization_id": "org-uuid",
"name": "data-pipeline",
"description": "Nightly data pipeline",
"metadata": {"env": "production"},
"created_by_type": "user",
"created_by": "user-uuid",
"created_at": "2025-01-15T10:30:00Z"
}
List Agents¶
GET /api/v1/organizations/{organization}/agents
| Parameter | Type | Required | Description |
|---|---|---|---|
after |
query | No | Pagination cursor |
amount |
query | No | Page size |
Get Agent¶
GET /api/v1/organizations/{organization}/agents/{agent_name}
Update Agent¶
PUT /api/v1/organizations/{organization}/agents/{agent_name}
Updates an agent's description, metadata, and/or inline policy. All fields are optional — only provided fields are updated.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
description |
string | No | New description (replaces existing) |
metadata |
object | No | New metadata (replaces existing entirely) |
inline_policy |
string | No | Policy DSL source code evaluated alongside attached policies. Set to "" to clear. |
The inline policy is validated before saving. If the policy contains syntax errors, the update returns 400 with validation details.
Note
Agents cannot update other agents. Only users and roles can call this endpoint.
Delete Agent¶
DELETE /api/v1/organizations/{organization}/agents/{agent_name}
Deleting an agent revokes all of its API keys and removes it from all groups and policy attachments.
Note
Agents cannot delete other agents. Only users and roles can call this endpoint.
Create Agent API Key¶
POST /api/v1/organizations/{organization}/agents/{agent_name}/auth/keys
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | A human-readable name for the API key |
Response: 201 Created
{
"token_id": "key-id",
"token": "tak-key-id_secret",
"name": "pipeline-key",
"created_at": "2025-01-15T10:30:00Z"
}
Warning
The token field is only returned once at creation time. Store it securely — it cannot be retrieved again.
List Agent API Keys¶
GET /api/v1/organizations/{organization}/agents/{agent_name}/auth/keys
Response: 200 OK
{
"results": [
{
"token_id": "key-id",
"name": "pipeline-key",
"token_hint": "tak-...abc",
"created_at": "2025-01-15T10:30:00Z",
"last_used_at": "2025-01-16T08:00:00Z"
}
]
}
Revoke Agent API Key¶
DELETE /api/v1/organizations/{organization}/agents/{agent_name}/auth/keys/{key_id}
Agent Secrets¶
Agent secrets are encrypted key-value pairs scoped to an agent. When a sandbox runs as this agent, its secrets are injected as environment variables, overriding repository secrets with the same key. See the API Reference for full CRUD details and examples.
| Method | Endpoint | Action |
|---|---|---|
PUT |
/organizations/{org}/agents/{name}/secrets/{key} |
ManageAgentSecrets |
GET |
/organizations/{org}/agents/{name}/secrets |
ManageAgentSecrets |
GET |
/organizations/{org}/agents/{name}/secrets/{key} |
ReadAgentSecrets |
DELETE |
/organizations/{org}/agents/{name}/secrets/{key} |
ManageAgentSecrets |
Both ManageAgentSecrets and ReadAgentSecrets are included in the built-in AgentManager policy, scoped to agents the principal created.
Note
Deleting an agent also soft-deletes all of its secrets.
Policies¶
Create Policy¶
POST /api/v1/organizations/{organization}/policies
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Policy name |
description |
string | No | Description |
policy_text |
string | Yes | Policy DSL source code |
Validate Policy¶
POST /api/v1/organizations/{organization}/policies:validate
Test a policy for syntax errors before creating it.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
policy_text |
string | Yes | Policy DSL source code to validate |
Response: 200 OK
Or on failure:
{
"valid": false,
"errors": [
{ "message": "parse_error: unexpected token", "line": 5, "column": 10 }
]
}
List Policies¶
GET /api/v1/organizations/{organization}/policies
Returns policy summaries (without policy source). Use Get Policy for the full body.
Get Policy¶
GET /api/v1/organizations/{organization}/policies/{policy_id}
Returns the full policy including the policy DSL source.
Update Policy¶
PUT /api/v1/organizations/{organization}/policies/{policy_id}
Errors: 403 if attempting to modify a built-in policy.
Delete Policy¶
DELETE /api/v1/organizations/{organization}/policies/{policy_id}
Errors: 403 (built-in policy), 409 (last Owner policy)
Attachments¶
Attach Policy to Principal¶
POST /api/v1/organizations/{organization}/policies/{policy_id}/attachments
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
principal_type |
string | Yes | "user", "group", "role", or "agent" |
principal_id |
string (UUID) | Yes | User, group, role, or agent ID |
Detach Policy¶
DELETE /api/v1/organizations/{organization}/policies/{policy_id}/attachments
| Parameter | Type | Required | Description |
|---|---|---|---|
principal_type |
query | Yes | "user", "group", "role", or "agent" |
principal_id |
query | Yes | UUID |
List All Attachments¶
GET /api/v1/organizations/{organization}/attachments
Returns all policy-to-principal attachments in the organization.
Get Effective Policies¶
GET /api/v1/organizations/{organization}/effective-policies
Returns policies effective for a principal. Accepts optional query parameters principal_type and principal_id to query a specific principal (defaults to the current user).
For users and roles, this returns the union of direct attachments and group-inherited policies.
For agents, the endpoint returns only the agent's inline policy (if set) — and nothing else. Agents have no "effective policies" list in the traditional sense: direct attachments and group memberships on an agent are ignored at evaluation time. To understand what an agent can actually do, look at the inline policy here AND separately query the creator's effective policies (by principal type user or role, using the agent's created_by / created_by_type); the agent's real permissions are the intersection of those two.
Each entry includes a source field:
| Source | Description |
|---|---|
direct |
Attached directly to the principal (users and roles only) |
group |
Inherited through a group membership (users and roles only) |
inline |
(Agents only) The agent's inline policy |
{
"results": [
{
"policy_id": "...",
"policy_name": "Owner",
"source": "direct"
},
{
"policy_id": "...",
"policy_name": "repo-writers",
"source": "group",
"source_name": "data-engineers"
}
]
}
Rule Composition¶
Rules for the same action are evaluated independently — every rule is checked, and the results are combined. This lets you layer broad permissions with narrow restrictions or approval gates.
How rules combine within a single policy¶
When multiple rules match the same action, all of their results are merged:
- If any matching rule is a deny (
!), the result includes deny - If any matching rule is an allow, the result includes allow
- If any matching rule is an approval (
?), the result includes require-approval
The final decision follows precedence: deny > require-approval > allow.
Broad allow + narrow approval¶
Allow writes to a repository, but require approval for a sensitive path:
| Path written | Allow rule | Approval rule | Result |
|---|---|---|---|
public/data.txt |
matches (repo=foo) | no match (path≠private/*) | allowed |
private/secret.txt |
matches (repo=foo) | matches (repo=foo, path=private/*) | requires approval |
The unscoped PutObject(repository:"foo") allows all writes to foo. The ?PutObject adds an approval gate only when the path matches private/*.
Scoped allow + narrower approval¶
Allow writes under a specific path, but require approval for a subdirectory:
PutObject(repository:"data", path:"results/*")
?PutObject(repository:"data", path:"results/production/*")
| Path written | Allow rule | Approval rule | Result |
|---|---|---|---|
results/dev/out.csv |
matches | no match | allowed |
results/production/model.bin |
matches | matches | requires approval |
other/file.txt |
no match | no match | denied (no allow) |
Broad allow + narrow deny¶
Allow everything in a repository except a protected path:
| Path written | Allow rule | Deny rule | Result |
|---|---|---|---|
docs/readme.md |
matches | no match | allowed |
locked/config.yaml |
matches | matches | denied (deny overrides allow) |
Rules with no modifiers match everything¶
A rule with no modifiers for a field matches any value for that field. This is how you express "all repositories" or "all paths":
GetObject() # allow reading any object in any repo
GetObject(repository:"docs") # allow reading any object, but only in "docs"
GetObject(path:"*.csv") # allow reading CSV files in any repo
Rules across multiple policies (users and roles)¶
When a user or role has multiple policies attached (directly and via groups), all policies are evaluated and their results merged using the same precedence:
- If any policy denies → denied
- If any policy allows (and none deny) → allowed
- If no policy allows → denied
A deny rule in one policy cannot be overridden by an allow rule in another policy.
Agent evaluation (inline intersected with creator)¶
Agents do not use the union rule above. They have two independent buckets:
- Inline bucket — the single inline policy written on the agent record
- Creator bucket — the creator's effective policy, resolved with the deny-overrides-allow rule above
Both buckets are evaluated for each action, and the final decision is:
- If either bucket denies → denied
- If the inline policy does not allow and does not require approval → denied (default deny)
- If the creator's effective policy does not allow → denied
- Otherwise → allowed, and if the inline policy required approval the resulting session is tainted
Worked example — creator has GetRepository() on all repos, inline says GetRepository(repository:"foo"):
| Repo | Creator | Inline | Result |
|---|---|---|---|
foo |
matches | matches | allowed |
bar |
matches | no match | denied |
Worked example — creator has GetRepository(repository:"foo"), inline says GetRepository():
| Repo | Creator | Inline | Result |
|---|---|---|---|
foo |
matches | matches | allowed |
bar |
no match | matches | denied (creator ceiling) |
Worked example — inline policy is empty: every action is denied.
Example Policies¶
Read-Only Access¶
Allow listing and reading all repositories and objects:
Repository-Scoped Write Access¶
Allow full write access only to a specific repository:
# Read access to all repos
ListRepositories()
GetRepository()
# Write access to "my-data" only
CreateSession(repository:"my-data")
CommitSession(repository:"my-data")
RollbackSession(repository:"my-data")
ApproveSessionChanges(repository:"my-data")
PutObject(repository:"my-data")
DeleteObject(repository:"my-data")
Path-Based Object Access¶
Restrict object writes to a specific path:
Require Approval for All Writes¶
Agents with this policy can read freely but any write operation requires a human to approve and commit the session:
Approval Flow
When an agent writes to a session and ? (require approval) is triggered, the session becomes tainted. The agent can continue staging changes, but when it attempts to commit, the API returns 202 Accepted instead of committing. A human user must then call ApproveSessionChanges to review and commit the session.
Require Approval for Specific Repositories¶
Require approval only for writes to sensitive repositories:
# Require approval for writes to production-data
?PutObject(repository:"production-data")
?DeleteObject(repository:"production-data")
Deny Rule Example¶
Deny deleting production repositories regardless of other policies:
Deny Overrides
Because Tilde uses deny-overrides-allow semantics, this ! (deny) rule will block repository deletion even if another policy allows it.
Restrict Session Commits to Specific Users¶
Allow only the admin user to commit sessions (useful for requiring review):
Deny Viewing Images¶
Block users from viewing image files (by extension) while still allowing access to other objects:
!GetObject(path:"*.png")
!GetObject(path:"*.jpg")
!GetObject(path:"*.jpeg")
!GetObject(path:"*.gif")
Tip
Each deny rule uses fnmatch pattern matching on the object path. You can add additional extensions like *.webp, *.svg, or *.bmp as needed.
Best Practices¶
-
Use groups — Attach policies to groups rather than individual users. This simplifies management when team members change.
-
Principle of least privilege — Start with the built-in ReadAll policy and add targeted permissions as needed rather than granting broad access.
-
Use deny rules sparingly — Deny rules override all allow rules across all policies. Use them for hard security boundaries (e.g., protecting production data).
-
Validate before creating — Use the
/policies:validateendpoint to check policy syntax before creating policies. -
Keep policies focused — Create small, composable policies rather than monolithic ones. For example, separate "repo read access" from "repo write access" policies.
-
Audit effective policies — Use the
/effective-policiesendpoint to verify what a principal can actually do after policy changes. For agents, the endpoint returns the inline policy; to understand the full authorization outcome, separately look up the creator's effective policy and mentally intersect the two. -
Protect the Owner policy — Ensure at least one trusted user or group always has the Owner policy attached. The system prevents detaching the last Owner policy, but plan your access hierarchy carefully.
-
Use roles for non-human access — Create roles for CI/CD pipelines, automation scripts, and service accounts instead of sharing user credentials. Roles provide clear audit trails (
principal_type: "role") and their API keys can be rotated independently. -
Use agents for AI/ML workloads — Agents default to zero permissions. Grant capabilities by writing an inline policy on the agent; the agent can still never exceed its creator. Use
?(require approval) rules in the inline policy to stage writes for human review. -
Require approval for sensitive writes — Use
?(require approval) rules on agents that interact with production data. This lets the agent stage changes autonomously while requiring a human to review and commit.
Caching¶
Authorization results are cached for 3 seconds per principal (user, role, or agent) per organization. This means:
- Policy changes take up to 3 seconds to take effect
- Group membership changes take up to 3 seconds to take effect
The following are cached:
| Data | Cache Key | TTL |
|---|---|---|
| Principal's group memberships | org_id:principal_id |
3s |
| Principal's resolved policies | org_id:principal_id |
3s |
| Organization name lookup | org_id |
3s |