Skip to content

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:

  1. 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.
  2. 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 says GetRepository(repository:"foo"), the agent can read only foo.
  • If the creator is limited to GetRepository(repository:"foo") and the inline policy says GetRepository(), the agent is still limited to foo — 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:

  • CreateSandbox on the repository
  • CreateSession on the repository
  • CommitSession on 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:

  1. Policies attached directly to the principal
  2. 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:

  1. All effective policies are evaluated
  2. If any rule denies the action (!Action()), the action is denied
  3. Otherwise, if at least one rule allows the action (Action()), the action is allowed
  4. If no rule allows the action, it is denied (default deny)

For agents, the rule is an intersection of two buckets:

  1. Evaluate the creator's effective policy (direct + group-inherited) using deny-overrides-allow
  2. Evaluate the inline policy on the agent itself
  3. 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:

  1. After allow/deny is resolved, the inline policy is checked for ?Action() rules
  2. If the inline policy requires approval for the action (and the creator allows it), the write proceeds but the session is tainted
  3. A tainted session cannot be committed by the agent — it requires a human user to approve and commit the changes via ApproveSessionChanges
  4. If the agent attempts to commit a tainted session, the API returns 202 Accepted with a message indicating approval is required
  5. 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

import tilde

org = tilde.organizations.get("my-team")
group = org.groups.create(
    "data-engineers",
    description="Data engineering team",
)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST https://tilde.run/api/v1/organizations/my-team/groups \
  -H "Content-Type: application/json" \
  -d '{"name": "data-engineers", "description": "Data engineering team"}'

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
group = org.groups.get(group_id)
group.members.create(subject_type="user", subject_id=user_id)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST https://tilde.run/api/v1/organizations/my-team/groups/group-uuid/members \
  -H "Content-Type: application/json" \
  -d '{"subject_type": "user", "subject_id": "user-uuid"}'

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
import tilde

org = tilde.organizations.get("my-team")
role = org.roles.create(
    "ci-deployer",
    description="CI/CD pipeline for production deployments",
)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST https://tilde.run/api/v1/organizations/my-team/roles \
  -H "Content-Type: application/json" \
  -d '{"name": "ci-deployer", "description": "CI/CD pipeline for production deployments"}'

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
org = tilde.organizations.get("my-team")
for role in org.roles.list():
    print(role.name)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://tilde.run/api/v1/organizations/my-team/roles

Get Role

GET /api/v1/organizations/{organization}/roles/{role_name}

org = tilde.organizations.get("my-team")
role = org.roles.get("ci-deployer")
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://tilde.run/api/v1/organizations/my-team/roles/ci-deployer

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.

org = tilde.organizations.get("my-team")
org.roles.delete("ci-deployer")
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X DELETE https://tilde.run/api/v1/organizations/my-team/roles/ci-deployer

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
org = tilde.organizations.get("my-team")
role = org.roles.get("ci-deployer")
key = role.api_keys.create("github-actions-key")
print(key.token)  # Only returned once at creation time
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST https://tilde.run/api/v1/organizations/my-team/roles/ci-deployer/auth/keys \
  -H "Content-Type: application/json" \
  -d '{"name": "github-actions-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

org = tilde.organizations.get("my-team")
role = org.roles.get("ci-deployer")
for key in role.api_keys.list():
    print(key.name, key.token_hint)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://tilde.run/api/v1/organizations/my-team/roles/ci-deployer/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}

org = tilde.organizations.get("my-team")
role = org.roles.get("ci-deployer")
key = role.api_keys.get("key-id")
key.revoke()
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X DELETE https://tilde.run/api/v1/organizations/my-team/roles/ci-deployer/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)
import tilde

org = tilde.organizations.get("my-team")
agent = org.agents.create(
    "data-pipeline",
    description="Nightly data pipeline",
    metadata={"env": "production"},
)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST https://tilde.run/api/v1/organizations/my-team/agents \
  -H "Content-Type: application/json" \
  -d '{"name": "data-pipeline", "description": "Nightly data pipeline", "metadata": {"env": "production"}}'

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
org = tilde.organizations.get("my-team")
for agent in org.agents.list():
    print(agent.name)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://tilde.run/api/v1/organizations/my-team/agents

Get Agent

GET /api/v1/organizations/{organization}/agents/{agent_name}

org = tilde.organizations.get("my-team")
agent = org.agents.get("data-pipeline")
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://tilde.run/api/v1/organizations/my-team/agents/data-pipeline

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.
org = tilde.organizations.get("my-team")
agent = org.agents.get("data-pipeline")
agent.update(inline_policy="""
?PutObject()
?DeleteObject()
""")
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X PUT https://tilde.run/api/v1/organizations/my-team/agents/data-pipeline \
  -H "Content-Type: application/json" \
  -d '{"inline_policy": "?PutObject()\n?DeleteObject()"}'

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.

org = tilde.organizations.get("my-team")
org.agents.delete("data-pipeline")
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X DELETE https://tilde.run/api/v1/organizations/my-team/agents/data-pipeline

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
org = tilde.organizations.get("my-team")
agent = org.agents.get("data-pipeline")
key = agent.api_keys.create("pipeline-key")
print(key.token)  # Only returned once at creation time
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST https://tilde.run/api/v1/organizations/my-team/agents/data-pipeline/auth/keys \
  -H "Content-Type: application/json" \
  -d '{"name": "pipeline-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

org = tilde.organizations.get("my-team")
agent = org.agents.get("data-pipeline")
for key in agent.api_keys.list():
    print(key.name, key.token_hint)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://tilde.run/api/v1/organizations/my-team/agents/data-pipeline/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}

org = tilde.organizations.get("my-team")
agent = org.agents.get("data-pipeline")
key = agent.api_keys.get("key-id")
key.revoke()
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X DELETE https://tilde.run/api/v1/organizations/my-team/agents/data-pipeline/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
import tilde

org = tilde.organizations.get("my-team")
policy = org.policies.create(
    name="repo-writers",
    description="Allow writing to specific repositories",
    policy_text="""
PutObject(repository:"my-data")
""",
)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST https://tilde.run/api/v1/organizations/my-team/policies \
  -H "Content-Type: application/json" \
  -d '{
    "name": "repo-writers",
    "description": "Allow writing to specific repositories",
    "policy_text": "PutObject(repository:\"my-data\")"
  }'

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

{
  "valid": true,
  "errors": []
}

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
policy = org.policies.get(policy_id)
policy.attach(principal_type="agent", principal_id=agent_id)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST https://tilde.run/api/v1/organizations/my-team/policies/policy-uuid/attachments \
  -H "Content-Type: application/json" \
  -d '{"principal_type": "agent", "principal_id": "agent-uuid"}'

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:

PutObject(repository:"foo")
?PutObject(repository:"foo", path:"private/*")
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:

PutObject(repository:"shared")
!PutObject(repository:"shared", path:"locked/*")
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:

  1. If any policy denies → denied
  2. If any policy allows (and none deny) → allowed
  3. 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:

  1. If either bucket denies → denied
  2. If the inline policy does not allow and does not require approvaldenied (default deny)
  3. If the creator's effective policy does not allowdenied
  4. 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:

ListRepositories()
GetRepository()
ListObjects()
GetObject()
LogCommits()

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:

PutObject(path:"team-a/*")
GetObject()
ListObjects()

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:

?PutObject()
?DeleteObject()

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:

!DeleteRepository(repository:"production")

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):

CommitSession(created_by:"admin")

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

  1. Use groups — Attach policies to groups rather than individual users. This simplifies management when team members change.

  2. Principle of least privilege — Start with the built-in ReadAll policy and add targeted permissions as needed rather than granting broad access.

  3. Use deny rules sparingly — Deny rules override all allow rules across all policies. Use them for hard security boundaries (e.g., protecting production data).

  4. Validate before creating — Use the /policies:validate endpoint to check policy syntax before creating policies.

  5. Keep policies focused — Create small, composable policies rather than monolithic ones. For example, separate "repo read access" from "repo write access" policies.

  6. Audit effective policies — Use the /effective-policies endpoint 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.

  7. 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.

  8. 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.

  9. 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.

  10. 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