API Reference¶
All endpoints are under /api/v1. Authentication is required unless noted otherwise.
See Using the API for authentication details and common patterns.
Authentication¶
Sign Up¶
POST /api/v1/auth/signup — No auth required
Create a new user account.
Tilde is currently in a private preview. Most signups land in a pending state
and cannot sign in until an administrator approves them (typically within
24-48 hours). The user gets a confirmation email immediately and a second email
with a link to sign in once approved.
Signups whose email matches an open organization invitation skip the approval
step and are activated (status = "active") right away, so they can accept the
invitation.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
username |
string | Yes | Unique username |
email |
string | Yes | Unique email address |
full_name |
string | Yes | Display name |
password |
string | Yes | Password |
company |
string | No | Company name |
Response: 201 Created
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"username": "alice",
"email": "alice@example.com",
"full_name": "Alice Smith",
"source": "password",
"status": "pending",
"created_at": "2025-01-15T10:30:00Z"
}
Inspect status to decide what to do next: "pending" means to wait for the
approval email; "active" means the user can immediately call /auth/login.
Errors: 400 (invalid input), 409 (EMAIL_TAKEN or USERNAME_TAKEN)
Log In¶
POST /api/v1/auth/login — No auth required (IP rate-limited and per-account locked)
Authenticate and receive session cookies.
Two protections layer on top of each other:
- An IP-based rate limit caps any single client to roughly 10 attempts per minute.
- A per-account counter locks the account for 15 minutes after 10 failed attempts within a 15-minute window. A successful login resets the counter. While locked, requests for that account return
429 ACCOUNT_LOCKEDwith aRetry-Afterheader — wait for the cooldown rather than retrying in a loop.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
login |
string | Yes | Email or username |
password |
string | Yes | Password |
Response: 200 OK
Sets tilde_session (HttpOnly JWT) and tilde_csrf cookies.
{
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"username": "alice",
"email": "alice@example.com",
"full_name": "Alice Smith",
"source": "password",
"status": "active",
"created_at": "2025-01-15T10:30:00Z"
}
}
Errors: 401 (INVALID_CREDENTIALS), 403 (PENDING_APPROVAL — the account was created but has not been approved yet; the user will receive an email when they can sign in), 429 (ACCOUNT_LOCKED — too many recent failed attempts; honour Retry-After)
Log Out¶
POST /api/v1/auth/logout
Clear session cookies.
Response: 204 No Content
Get Current User¶
GET /api/v1/auth/me
Returns the authenticated user and their organization memberships.
Response: 200 OK
{
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"username": "alice",
"email": "alice@example.com",
"full_name": "Alice Smith",
"source": "password",
"status": "active",
"created_at": "2025-01-15T10:30:00Z"
},
"organizations": [
{
"id": "...",
"name": "my-team",
"display_name": "My Team"
}
]
}
Check Username Availability¶
GET /api/v1/auth/check-username — No auth required
| Parameter | Type | Required | Description |
|---|---|---|---|
username |
query | Yes | Username to check |
Response: 200 OK
Create API Key¶
POST /api/v1/auth/keys
Create a new API key. The token is returned only once in this response — save it securely.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Human-readable key name |
description |
string | No | Key description |
Response: 201 Created
{
"token": "tuk-..._...",
"id": "opaque-key-id",
"name": "ci-pipeline",
"description": "CI/CD pipeline key"
}
The token field is your API token — use it as Authorization: Bearer <token>. The id field is used for management operations (listing, revocation) and is not the token itself.
List API Keys¶
GET /api/v1/auth/keys
Returns all API keys for the authenticated user. Tokens are never included — only a hint (last 4 characters) is shown for identification.
Response: 200 OK
{
"results": [
{
"id": "opaque-key-id",
"user_id": "...",
"name": "ci-pipeline",
"description": "CI/CD pipeline key",
"token_hint": "Ab3x",
"created_at": "2025-01-15T10:30:00Z",
"last_used_at": "2025-01-16T08:00:00Z",
"revoked_at": null
}
]
}
Revoke API Key¶
DELETE /api/v1/auth/keys/{key_id}
Soft-revokes an API key. Revoked keys can no longer authenticate.
Response: 204 No Content
Add Email¶
POST /api/v1/auth/emails
Add a secondary email to the authenticated user.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Email address to add |
Response: 201 Created
{
"id": "...",
"user_id": "...",
"email": "alice-work@example.com",
"is_primary": false,
"verified": false,
"created_at": "2025-01-15T10:30:00Z"
}
List Emails¶
GET /api/v1/auth/emails
Response: 200 OK
{
"results": [
{ "email": "alice@example.com", "is_primary": true, "verified": true },
{ "email": "alice-work@example.com", "is_primary": false, "verified": false }
]
}
Organizations¶
Create Organization¶
POST /api/v1/organizations
The creator is automatically added as an owner. Built-in RBAC policies (Owner, Member) are seeded.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Unique slug (^[a-z0-9][a-z0-9-]{1,62}$). Reserved: api, auth, admin, system. |
display_name |
string | Yes | Human-readable name |
Response: 201 Created
Errors: 400 (invalid name format or reserved name), 409 (name taken)
List Organizations¶
GET /api/v1/organizations
Returns organizations the authenticated user is a member of.
Response: 200 OK
{
"results": [
{ "id": "...", "name": "my-team", "display_name": "My Team", "created_at": "..." }
]
}
Get Organization¶
GET /api/v1/organizations/{organization}
Response: 200 OK — same shape as a single organization object.
List Members¶
GET /api/v1/organizations/{organization}/members
Response: 200 OK
{
"results": [
{
"organization_id": "...",
"user_id": "...",
"username": "alice",
"full_name": "Alice Smith",
"email": "alice@example.com",
"joined_at": "2025-01-15T10:30:00Z"
}
]
}
What a member can do is determined entirely by policies and groups, not by a role on the membership record itself.
Remove Member¶
DELETE /api/v1/organizations/{organization}/members/{user_id}
Remove a user from the organization. The user's group memberships and direct policy attachments in this organization are removed at the same time.
Response: 204 No Content
Errors: 404 (member not found)
Invite Member¶
POST /api/v1/organizations/{organization}/invitations
Members are added by inviting them — the invited user gets an email and joins the organization once they accept. You can target the invitation by email or by an existing username, and optionally pre-attach groups and policies that will be applied on accept.
Request body (must include exactly one of email or username):
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Conditional | Email address of the invitee |
username |
string | Conditional | Username of an existing user (the invitation is keyed to that user's email) |
group_ids |
string[] (UUID) | No | Groups to add the invited user to on accept |
policy_ids |
string[] (UUID) | No | Policies to attach to the invited user on accept |
Response: 201 Created
{
"id": "invitation-uuid",
"organization_id": "...",
"email": "alice@example.com",
"invited_by": "...",
"status": "pending",
"created_at": "2025-01-15T10:30:00Z",
"expires_at": "2025-01-22T10:30:00Z"
}
Errors: 400 (invalid input), 404 (username was provided but no such user), 409 (already a member or duplicate pending invitation)
List Pending Invitations (Organization)¶
GET /api/v1/organizations/{organization}/invitations
Response: 200 OK — array of Invitation objects (same shape as the create response).
Revoke Invitation¶
DELETE /api/v1/organizations/{organization}/invitations/{invitation_id}
Cancels a pending invitation. Once revoked, the invitee can no longer accept it.
Response: 204 No Content
Errors: 404 (invitation not found), 409 (invitation is no longer in pending status)
List My Invitations¶
GET /api/v1/invitations
Returns pending invitations for the authenticated user across all organizations. Each invitation includes the inviting organization's name and display name plus the inviter's name.
Accept Invitation¶
POST /api/v1/invitations/{invitation_id}/accept
Joins the inviting organization. Any group_ids / policy_ids set when the invitation was created are now attached to your user.
Response: 200 OK with { "status": "accepted" }.
Errors: 404 (not your invitation), 410 (invitation expired or revoked)
Decline Invitation¶
POST /api/v1/invitations/{invitation_id}/decline
Response: 200 OK with { "status": "declined" }.
Repositories¶
Create Repository¶
POST /api/v1/organizations/{organization}/repositories
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Repository name |
description |
string | No | Description |
retention_days |
integer | No | Days of commit history to retain (default: 14). |
Response: 201 Created
{
"id": "...",
"organization_id": "...",
"name": "my-data",
"description": "",
"retention_days": 14,
"created_by_type": "user",
"created_by": "...",
"created_at": "2025-01-15T10:30:00Z"
}
List Repositories (in Organization)¶
GET /api/v1/organizations/{organization}/repositories
| Parameter | Type | Required | Description |
|---|---|---|---|
after |
query | No | Pagination cursor |
amount |
query | No | Page size (default: 100, max: 1000) |
List All Repositories (Cross-Org)¶
GET /api/v1/repositories
Returns all repositories the user has access to across all organizations. Each result includes organization_slug and organization_display_name.
Get Repository¶
GET /api/v1/organizations/{organization}/repositories/{repository}
Update Repository¶
PUT /api/v1/organizations/{organization}/repositories/{repository}
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
description |
string | No | New description |
retention_days |
integer | No | Days of commit history to retain. |
Delete Repository¶
DELETE /api/v1/organizations/{organization}/repositories/{repository}
Response: 204 No Content (soft delete)
Sessions¶
Create Session¶
POST /api/v1/organizations/{organization}/repositories/{repository}/sessions
Create a new session for staging changes. A session is an isolated workspace where you can upload, modify, and delete objects before committing.
Response: 201 Created
Commit Session¶
POST /api/v1/organizations/{organization}/repositories/{repository}/sessions/{session_id}
Commit all staged changes in the session. This creates a new commit and closes the session.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
message |
string | Yes | Commit message |
metadata |
object | No | Arbitrary key-value metadata |
Response: 200 OK
Response (agent with approval required): 202 Accepted
When the repository has agents_require_approval enabled and the caller is an agent, the session is not committed. Instead, the response contains URLs for the approval page:
{
"approval_required": true,
"session_id": "SESSION_ID",
"message": "this commit requires human approval before it can be applied",
"api_url": "/api/v1/organizations/my-team/repositories/my-data/sessions/SESSION_ID/approve",
"web_url": "https://tilde.run/console/organizations/my-team/repositories/my-data/sessions/SESSION_ID/approve"
}
A human reviewer must visit the web_url to approve or roll back the changes. Agents can poll HEAD /api/v1/organizations/{organization}/repositories/{repository}/sessions/{session_id}/approve — it returns 200 while the session is pending, and 404 once it has been committed or rolled back.
Errors:
| Status | Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST |
Missing message or invalid session |
| 404 | NOT_FOUND |
Session does not exist |
| 409 | CONFLICT |
Session conflicts with another concurrent commit |
Rollback Session¶
DELETE /api/v1/organizations/{organization}/repositories/{repository}/sessions/{session_id}
Discard all staged changes and close the session without creating a commit.
Response: 204 No Content
Errors: 404 (session not found)
Review Session Changes¶
GET /api/v1/organizations/{organization}/repositories/{repository}/sessions/{session_id}/approve
List uncommitted changes in a session for review before approving. This is the same data as the changes endpoint, but addressed by session path parameter.
| Parameter | Type | Required | Description |
|---|---|---|---|
prefix |
query | No | Path prefix filter |
after |
query | No | Pagination cursor |
amount |
query | No | Page size (default: 100, max: 1000) |
Response: 200 OK
{
"results": [
{
"path": "data/file.csv",
"entry": { "size": 1024, "e_tag": "\"abc123\"" },
"diff_type": "added"
}
],
"pagination": { "has_more": false, "next_offset": "", "max_per_page": 1000 }
}
Errors: 404 (session not found)
Approve Session¶
POST /api/v1/organizations/{organization}/repositories/{repository}/sessions/{session_id}/approve
Approve and commit a session's staged changes. The resulting commit metadata includes approved_by, approved_by_type, and approved_by_id fields identifying the approver.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
message |
string | Yes | Commit message |
metadata |
object | No | Arbitrary key-value metadata |
Response: 200 OK
Errors:
| Status | Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST |
No changes to commit or invalid input |
| 404 | NOT_FOUND |
Session does not exist or already committed |
| 409 | CONFLICT |
Session conflicts with another concurrent commit |
Commits¶
Get Commit¶
GET /api/v1/organizations/{organization}/repositories/{repository}/commits/{commit_id}
Response: 200 OK
{
"id": "abc123def456...",
"committer": "alice",
"committer_type": "user",
"committer_id": "550e8400-e29b-41d4-a716-446655440000",
"message": "Add dataset",
"creation_date": "2025-01-15T10:30:00Z",
"parents": ["parent-commit-id"],
"metadata": {},
"is_stale": false
}
Get Commit Log¶
GET /api/v1/organizations/{organization}/repositories/{repository}/log
| Parameter | Type | Required | Description |
|---|---|---|---|
ref |
query | No | Commit ID to start from (defaults to latest commit) |
after |
query | No | Pagination cursor |
amount |
query | No | Page size |
Response: 200 OK
{
"results": [
{
"id": "abc123...",
"committer": "alice",
"message": "Add dataset",
"creation_date": "2025-01-15T10:30:00Z",
"parents": ["..."],
}
],
"pagination": { "has_more": false, "next_offset": "", "max_per_page": 100 }
}
Diff¶
GET /api/v1/organizations/{organization}/repositories/{repository}/diff
Compare two commits and return the differences.
| Parameter | Type | Required | Description |
|---|---|---|---|
left |
query | Yes | Left ref (commit ID) |
right |
query | Yes | Right ref (commit ID) |
prefix |
query | No | Path prefix filter |
after |
query | No | Pagination cursor |
amount |
query | No | Page size (default: 1000) |
delimiter |
query | No | Hierarchy delimiter (e.g., "/") |
Response: 200 OK
{
"results": [
{
"path": "data/file.csv",
"type": "object",
"entry": {
"size": 1024,
"e_tag": "\"abc123\"",
"content_type": "text/csv",
"last_modified": "2025-01-15T10:30:00Z"
},
"status": "added"
}
],
"pagination": { "has_more": false, "next_offset": "", "max_per_page": 1000 }
}
Each result includes a status field: "added", "modified", or "removed".
Revert Commit¶
POST /api/v1/organizations/{organization}/repositories/{repository}/commits/{commit_id}/revert
Creates a new commit on the repository that undoes the changes introduced by the specified commit. The original commit is preserved in history.
Limitations: Merge commits (commits with more than one parent) and root commits (the initial commit with no parents) cannot be reverted.
Conflict behavior: The revert succeeds only if every path affected by the target commit is unchanged in the repository's latest commit since that commit. If any affected path has been modified by a subsequent commit, the endpoint returns 409 Conflict with details about the conflicting paths.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
message |
string | No | Commit message. Defaults to Revert "<original message>" |
metadata |
object | No | Key-value metadata to attach to the revert commit |
Response: 201 Created
Errors:
| Status | Code | Description |
|---|---|---|
400 |
BAD_REQUEST | Root commit, merge commit, or nothing to revert |
404 |
NOT_FOUND | Commit not found |
409 |
CONFLICT | Paths changed since the target commit |
Objects¶
Get Object¶
GET /api/v1/organizations/{organization}/repositories/{repository}/object
Download an object. The server always returns a 307 Temporary Redirect to a short-lived download URL. Follow the redirect to download the object directly.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id |
query | No | Session ID to read from (defaults to latest committed state) |
path |
query | Yes | Object path |
ref |
query | No | Commit ID to read from |
Response: 307 Temporary Redirect
The Location header contains a short-lived download URL. The response includes Cache-Control: no-store.
import tilde
repo = tilde.repository("my-team/my-data")
# The SDK follows the redirect automatically
with repo.objects.get("data/file.csv") as reader:
data = reader.read()
# Stream in chunks for large files
with repo.objects.get("data/file.csv") as reader:
for chunk in reader.iter_bytes(chunk_size=8192):
process(chunk)
# Follow the redirect automatically with -L
curl -L -H "Authorization: Bearer YOUR_API_TOKEN" \
"https://tilde.run/api/v1/organizations/my-team/repositories/my-data/object?path=data/file.csv" \
-o file.csv
# Or capture the download URL without downloading
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
"https://tilde.run/api/v1/organizations/my-team/repositories/my-data/object?path=data/file.csv" \
-s -o /dev/null -w '%{redirect_url}'
Errors: 403 (not authorized), 404 (object not found), 410 (commit expired)
Head Object¶
HEAD /api/v1/organizations/{organization}/repositories/{repository}/object
Check object existence and retrieve metadata without streaming the body. Returns the same headers as GET (ETag, Content-Type, Content-Length).
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id |
query | No | Session ID to read from (defaults to latest committed state) |
path |
query | Yes | Object path |
Response: 200 OK (headers only, no body)
Errors: 403 (not authorized), 404 (object not found)
Put Object¶
PUT /api/v1/organizations/{organization}/repositories/{repository}/object
Start an upload to a session. The server returns an opaque upload URL and an upload token. Upload the object body to the upload URL, then call Finalize Object to register it in the catalog.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id |
query | Yes | Target session |
path |
query | Yes | Object path |
Errors: 400 (missing session_id or path), 403 (not authorized), 404 (session not found)
Response: 200 OK
| Field | Description |
|---|---|
upload_url |
Opaque URL to PUT the object body to (expires in 15 minutes) |
upload_token |
Opaque token to pass to the Finalize Object endpoint |
Note
The Python SDK handles this entire flow automatically. session.objects.put() calls Put Object, uploads, then calls Finalize Object.
Delete Object¶
DELETE /api/v1/organizations/{organization}/repositories/{repository}/object
Stages a tombstone (delete marker) for the object in a session.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id |
query | Yes | Target session |
path |
query | Yes | Object path |
Errors: 400 (missing session_id or path), 404 (session not found)
Response: 204 No Content
Copy Object¶
POST /api/v1/organizations/{organization}/repositories/{repository}/object/copy
Copy a catalog entry from one path to another within the same session. The underlying data is not transferred — the destination entry references the same physical object. Use together with DELETE /object to implement rename.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id |
query | Yes | Session for the copy |
source_path |
query | Yes | Path of the source object |
destination_path |
query | Yes | Path for the destination object |
Response: 201 Created
Errors: 400 (missing parameters), 403 (not authorized), 404 (session or source object not found)
Finalize Object¶
POST /api/v1/organizations/{organization}/repositories/{repository}/object/finalize
After uploading to the upload URL received from Put Object, call this endpoint to register the object in the catalog.
Note
Python SDK users do not call this endpoint directly. session.objects.put() handles the full flow automatically.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id |
query | Yes | Target session |
path |
query | Yes | Object path |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
upload_token |
string | Yes | Upload token from the Put Object response |
checksum |
string | No | ETag from the upload response (without quotes) |
content_type |
string | No | Content type of the uploaded object |
size_bytes |
integer | No | Object size in bytes |
Response: 201 Created
Errors:
| Status | Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST |
Missing or malformed parameters |
| 403 | FORBIDDEN |
Insufficient permissions |
| 404 | NOT_FOUND |
Session not found or already committed |
Initiate Multipart Upload¶
POST /api/v1/organizations/{organization}/repositories/{repository}/object/multipart
Start a multipart upload for large files. Returns an upload ID and upload token that must be passed to all subsequent part, complete, and abort requests.
Use multipart uploads when a file is too large for a single PUT — the client uploads parts directly using upload URLs, then completes the upload to register the catalog entry.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id |
query | Yes | Target session |
path |
query | Yes | Object path |
Response: 200 OK
| Field | Description |
|---|---|
upload_id |
Multipart upload ID — pass to all subsequent multipart calls |
upload_token |
Opaque token to pass to the Complete Multipart Upload endpoint |
Errors: 400 (missing parameters), 403 (not authorized), 404 (session not found)
Get Part Upload URL¶
GET /api/v1/organizations/{organization}/repositories/{repository}/object/multipart/part
Get an upload URL for a single part. PUT the part data to the returned URL and capture the ETag response header.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id |
query | Yes | Session ID |
path |
query | Yes | Object path |
upload_id |
query | Yes | Upload ID from the initiate response |
part_number |
query | Yes | Part number (1-based, max 10,000) |
Response: 200 OK
Errors: 400 (missing or invalid parameters), 403 (not authorized)
Complete Multipart Upload¶
POST /api/v1/organizations/{organization}/repositories/{repository}/object/multipart/complete
Complete the multipart upload and register the catalog entry in the session. Call this after all parts have been uploaded successfully.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id |
query | Yes | Session ID |
path |
query | Yes | Object path |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
upload_id |
string | Yes | Upload ID from the initiate response |
upload_token |
string | Yes | Upload token from the initiate response |
parts |
array | Yes | List of completed parts, each with part_number (int) and etag (string) |
content_type |
string | No | Content type (defaults to application/octet-stream) |
checksum |
string | No | Final ETag / checksum of the completed upload |
size_bytes |
integer | No | Total size in bytes |
Response: 201 Created
Errors:
| Status | Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST |
Missing or malformed parameters, empty parts list |
| 403 | FORBIDDEN |
Insufficient permissions |
| 404 | NOT_FOUND |
Session not found or already committed |
Abort Multipart Upload¶
DELETE /api/v1/organizations/{organization}/repositories/{repository}/object/multipart
Abort a multipart upload and clean up any uploaded parts. Call this if an upload fails and you want to discard partial data.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id |
query | Yes | Session ID |
path |
query | Yes | Object path |
upload_id |
query | Yes | Upload ID from the initiate response |
Response: 204 No Content
Errors: 400 (missing parameters), 403 (not authorized)
Multipart Upload Example¶
import tilde
repo = tilde.repository("my-team/my-data")
with repo.session() as session:
# The SDK handles multipart uploads automatically for large files.
# Just call put() — if the file exceeds the part size threshold,
# the SDK uses multipart upload behind the scenes.
result = session.objects.put("data/large-file.parquet", open("large-file.parquet", "rb"))
print(f"Uploaded: {result.path}, ETag: {result.etag}")
session.commit("Add large dataset")
# 1. Initiate multipart upload
INIT=$(curl -s -H "Authorization: Bearer YOUR_API_TOKEN" \
-X POST "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/object/multipart?session_id=SESSION_ID&path=data/large-file.parquet")
UPLOAD_ID=$(echo "$INIT" | jq -r .upload_id)
UPLOAD_TOKEN=$(echo "$INIT" | jq -r .upload_token)
# 2. Get upload URL for each part and upload
PART_URL=$(curl -s -H "Authorization: Bearer YOUR_API_TOKEN" \
"https://tilde.run/api/v1/organizations/my-team/repositories/my-data/object/multipart/part?session_id=SESSION_ID&path=data/large-file.parquet&upload_id=$UPLOAD_ID&part_number=1" \
| jq -r .upload_url)
# Upload the part directly and capture the ETag
ETAG=$(curl -s -X PUT -T part1.bin "$PART_URL" -D - -o /dev/null | grep -i '^etag:' | tr -d '\r' | cut -d' ' -f2)
# 3. Complete the upload
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
-X POST "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/object/multipart/complete?session_id=SESSION_ID&path=data/large-file.parquet" \
-H "Content-Type: application/json" \
-d "{\"upload_id\": \"$UPLOAD_ID\", \"upload_token\": \"$UPLOAD_TOKEN\", \"parts\": [{\"part_number\": 1, \"etag\": $ETAG}], \"content_type\": \"application/octet-stream\"}"
Note
For most use cases, prefer the Put Object + Finalize Object flow (single PUT) or the Python SDK, which automatically chooses the right upload strategy. Use the multipart API directly only when you need fine-grained control over part uploads (e.g., resumable uploads or very large files).
List Objects¶
GET /api/v1/organizations/{organization}/repositories/{repository}/objects
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id |
query | No | Session ID to list from (defaults to latest committed state) |
prefix |
query | No | Path prefix filter |
after |
query | No | Pagination cursor |
amount |
query | No | Page size (default: 100, max: 1000) |
delimiter |
query | No | Hierarchy delimiter (e.g., "/") |
Response: 200 OK
{
"results": [
{
"path": "data/file.csv",
"type": "object",
"entry": {
"size": 1024,
"e_tag": "\"abc123\"",
"content_type": "text/csv",
"last_modified": "2025-01-15T10:30:00Z"
}
}
],
"common_prefixes": ["data/subdir/"],
"pagination": { "has_more": false, "next_offset": "", "max_per_page": 100 }
}
When delimiter is set, directories are returned in common_prefixes and only direct children are in results.
Bulk Delete Objects¶
POST /api/v1/organizations/{organization}/repositories/{repository}/objects/delete
Stage tombstones for many objects in a single request. Up to 1,000 paths per call.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id |
query | Yes | Session to stage the deletes into |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
paths |
string[] | Yes | Object paths to delete (1 to 1,000 entries) |
Response: 200 OK
Errors: 400 (missing or invalid input), 403 (not authorized), 404 (session not found)
List Uncommitted Changes¶
GET /api/v1/organizations/{organization}/repositories/{repository}/changes
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id |
query | Yes | Session ID |
prefix |
query | No | Path prefix filter |
after |
query | No | Pagination cursor |
amount |
query | No | Page size |
Response: Same shape as List Objects, showing only staged changes with status field ("added", "modified", "removed").
Connectors (Organization-Level)¶
Create Connector¶
POST /api/v1/organizations/{organization}/connectors
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Connector name (unique within org) |
type |
string | Yes | "s3" |
config |
object | Yes | Type-specific configuration (see Connectors Reference) |
Response: 201 Created
{
"id": "...",
"name": "production-s3",
"type": "s3",
"disabled": false,
"created_at": "2025-01-15T10:30:00Z"
}
Note
The config object is AES-encrypted at rest and never returned in API responses.
List Connectors¶
GET /api/v1/organizations/{organization}/connectors
Get Connector¶
GET /api/v1/organizations/{organization}/connectors/{connector_id}
Delete Connector¶
DELETE /api/v1/organizations/{organization}/connectors/{connector_id}
Response: 204 No Content (soft delete)
Note
Deleting a connector does not affect previously imported objects — they are stored in Tilde and remain fully readable. Future imports using this connector will fail.
Connector Attachments (Repository-Level)¶
Attach Connector to Repository¶
POST /api/v1/organizations/{organization}/repositories/{repository}/connectors
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
connector_id |
string (UUID) | Yes | Connector to attach |
Response: 201 Created
List Repository Connectors¶
GET /api/v1/organizations/{organization}/repositories/{repository}/connectors
Detach Connector from Repository¶
DELETE /api/v1/organizations/{organization}/repositories/{repository}/connectors/{connector_id}
Response: 204 No Content
Import¶
Start Import¶
POST /api/v1/organizations/{organization}/repositories/{repository}/import
Start an asynchronous import job that copies objects into the repository. Objects can be imported from an external connector (S3, GCS) or from another Tilde repository. The request body must contain exactly one of connector_id or (source_organization + source_repository). The import creates its own session internally, copies and stages the imported objects (up to 10 concurrently), and commits them automatically when complete.
Request body (connector import):
| Field | Type | Required | Description |
|---|---|---|---|
connector_id |
string (UUID) | Yes | Connector to import from |
destination_path |
string | Yes | Destination prefix in the repository |
source_prefix |
string | No | Source prefix filter |
commit_message |
string | No | Custom commit message |
Request body (cross-repository import):
| Field | Type | Required | Description |
|---|---|---|---|
source_organization |
string | Yes | Source organization name |
source_repository |
string | Yes | Source repository name |
destination_path |
string | Yes | Destination prefix in the repository |
source_prefix |
string | No | Source prefix filter |
commit_message |
string | No | Custom commit message |
Connector import¶
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
-X POST "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/import" \
-H "Content-Type: application/json" \
-d '{
"connector_id": "connector-uuid-here",
"destination_path": "imported/",
"source_prefix": "datasets/",
"commit_message": "Import production datasets"
}'
Cross-repository import¶
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
-X POST "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/import" \
-H "Content-Type: application/json" \
-d '{
"source_organization": "other-team",
"source_repository": "source-data",
"destination_path": "external/",
"source_prefix": "datasets/train/",
"commit_message": "Import training data"
}'
Response: 202 Accepted
Get Import Status¶
GET /api/v1/organizations/{organization}/repositories/{repository}/import/{job_id}
Response: 200 OK
{
"id": "job-uuid-here",
"repository_id": "...",
"connector_id": "...",
"source_prefix": "datasets/",
"destination_path": "imported/",
"commit_message": "Import production datasets",
"status": "completed",
"objects_imported": 1523,
"commit_id": "abc123...",
"error": "",
"source_organization": "",
"source_repository": "",
"created_by": "...",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:35:00Z"
}
Job statuses: pending → running → completed | failed
Sandboxes¶
Run containers with repository data mounted as a volume. Sandboxes execute asynchronously -- create one and poll for status or stream logs. See the Sandboxes guide for concepts and examples.
Create Sandbox¶
POST /api/v1/organizations/{organization}/repositories/{repository}/sandboxes
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
image |
string | Yes | Docker image reference (e.g. python:3.12, ubuntu:22.04, ghcr.io/my-org/my-image:latest) |
command |
string[] | No | Command to execute |
mountpoint |
string | No | Mount path inside container (default: /sandbox) |
path_prefix |
string | No | Only mount objects under this prefix |
timeout_seconds |
integer | No | Maximum execution time in seconds. |
env_vars |
object | No | Environment variables to inject |
run_as |
object | No | {"type": "agent"|"role", "id": "uuid"} — run as alternate principal. |
mode |
string | No | Execution mode. One of one-shot (default; run command or image default, then exit), interactive (allocate a pty, attach to terminal), service (idle; only serve /exec requests). |
Response: 201 Created
Errors: 400 (invalid image, mountpoint, or env vars), 404 (agent/role not found), 503 (sandbox execution not available)
List Sandboxes¶
GET /api/v1/organizations/{organization}/repositories/{repository}/sandboxes
Query parameters:
| Parameter | Type | Description |
|---|---|---|
after |
string | Pagination cursor |
amount |
integer | Page size (default/max: 1000) |
Response: 200 OK
{
"results": [
{
"id": "a1b2c3d4-...",
"image": "python:3.12",
"command": ["python", "process.py"],
"mountpoint": "/sandbox",
"status": "committed",
"commit_id": "abc123def456...",
"exit_code": 0,
"env_vars": {"OUTPUT_FORMAT": "json"},
"created_by_type": "user",
"created_by": "user-uuid",
"created_at": "2026-03-11T10:30:00Z",
"finished_at": "2026-03-11T10:32:00Z",
"trigger_id": null,
"trigger_name": ""
}
],
"pagination": { "has_more": false, "next_offset": "", "max_per_page": 1000 }
}
Get Sandbox¶
GET /api/v1/organizations/{organization}/repositories/{repository}/sandboxes/{sandbox_id}
Returns full sandbox details including status, exit code, commit ID (if committed), and error information.
Response: 200 OK — same shape as individual items in the list response, plus status_reason and error_message fields.
Errors: 404 (sandbox not found)
Get Sandbox Status¶
GET /api/v1/organizations/{organization}/repositories/{repository}/sandboxes/{sandbox_id}/status
Lightweight status-only endpoint for polling.
Response: 200 OK
Cancel Sandbox¶
DELETE /api/v1/organizations/{organization}/repositories/{repository}/sandboxes/{sandbox_id}
Cancels a running sandbox. The session is rolled back and no changes are committed.
Response: 202 Accepted
Errors: 404 (sandbox not found)
Stream Sandbox Logs¶
GET /api/v1/organizations/{organization}/repositories/{repository}/sandboxes/{sandbox_id}/logs/stdout
Stream the sandbox's merged stdout and stderr as a chunked response. The runner interleaves both streams in the order the sandbox produced them — there is no separate stderr endpoint.
Response: 200 OK (chunked text/plain)
Errors: 404 (sandbox not found), 410 (logs no longer available)
Stream Sandbox Network Logs¶
GET /api/v1/organizations/{organization}/repositories/{repository}/sandboxes/{sandbox_id}/logs/network
NDJSON stream of every outbound HTTP request the sandbox made, including the proxy's allow/deny decision and (for allowed requests) the upstream response metadata. One JSON object per line.
Response: 200 OK (chunked application/x-ndjson)
Errors: 404 (sandbox not found), 410 (logs no longer available)
Open Sandbox Terminal¶
GET /api/v1/organizations/{organization}/repositories/{repository}/sandboxes/{sandbox_id}/terminal
WebSocket endpoint for bidirectional terminal I/O with an interactive sandbox. Only available for sandboxes created with mode: "interactive" while they are still running.
The connection upgrades to WebSocket and uses binary frames with a single-byte type prefix:
| Byte | Direction | Payload |
|---|---|---|
0x00 |
client → server | stdin data |
0x01 |
server → client | stdout/stderr data (TTY-merged) |
0x02 |
client → server | JSON resize: {"cols":N,"rows":N} |
0x03 |
server → client | JSON exit: {"exited":true,"exit_code":N} |
Response: 101 Switching Protocols
Errors: 400 (sandbox is not interactive), 409 (terminal session unavailable, e.g. after crash recovery), 410 (sandbox already finished)
Sandbox Statuses¶
| Status | Description |
|---|---|
starting |
Sandbox is being prepared. Wait — do not attach. |
running |
Sandbox is ready. You may attach the terminal and/or stream logs. |
committed |
Finished successfully; changes committed. Read commit_id. |
awaiting_approval |
Finished but the session needs human approval. Surface web_url. |
failed |
Sandbox errored. Read error_message. |
cancelled |
Cancelled by a user or operator. |
Sandbox Triggers¶
Triggers automatically create sandboxes when specific files change in a commit. See the Triggers Reference for concepts, condition types, and examples.
Create Trigger¶
POST /api/v1/organizations/{organization}/repositories/{repository}/sandbox-triggers
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Unique name (1–100 characters) |
description |
string | No | Human-readable description |
enabled |
boolean | No | Whether the trigger is active (default: true) |
conditions |
array | Yes | 1–10 conditions (OR logic). See Condition types |
sandbox_config |
object | Yes | Sandbox configuration (same fields as Create Sandbox minus run_as) |
run_as |
object | No | {"type": "agent"|"role", "id": "uuid"} — delegate execution |
import tilde
repo = tilde.repository("my-team/my-data")
trigger = repo.sandbox_triggers.create(
name="Validate CSV uploads",
conditions=[{"type": "prefix", "prefix": "datasets/"}],
sandbox_config={
"image": "python:3.12",
"command": ["python", "-m", "validate"],
"timeout_seconds": 300,
},
)
print(trigger.id)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
-X POST https://tilde.run/api/v1/organizations/my-team/repositories/my-data/sandbox-triggers \
-H "Content-Type: application/json" \
-d '{
"name": "Validate CSV uploads",
"conditions": [
{ "type": "prefix", "prefix": "datasets/" }
],
"sandbox_config": {
"image": "python:3.12",
"command": ["python", "-m", "validate"],
"timeout_seconds": 300
}
}'
Response: 201 Created — returns the full trigger object.
{
"id": "trigger-uuid",
"repository_id": "repo-uuid",
"name": "Validate CSV uploads",
"description": "",
"enabled": true,
"conditions": [
{ "type": "prefix", "prefix": "datasets/" }
],
"sandbox_config": {
"image": "python:3.12",
"command": ["python", "-m", "validate"],
"timeout_seconds": 300
},
"created_by": "user-uuid",
"created_at": "2026-03-11T10:30:00Z",
"updated_at": "2026-03-11T10:30:00Z"
}
Errors: 400 (invalid name, conditions, or config), 409 (name already exists in this repository)
Condition Types¶
Prefix¶
Matches any changed file whose path starts with the given prefix.
Exact Path¶
Matches a specific file path, optionally filtered by change type.
diff_type |
Fires when |
|---|---|
any (default) |
File is added, removed, or modified |
added |
File is newly created |
removed |
File is deleted |
changed |
File is modified (existed before and after) |
List Triggers¶
GET /api/v1/organizations/{organization}/repositories/{repository}/sandbox-triggers
Response: 200 OK
{
"results": [ ... ],
"pagination": { "has_more": false, "next_offset": "", "max_per_page": 1000 }
}
Get Trigger¶
GET /api/v1/organizations/{organization}/repositories/{repository}/sandbox-triggers/{trigger_id}
Response: 200 OK — full trigger object.
Errors: 404 (trigger not found)
Update Trigger¶
PUT /api/v1/organizations/{organization}/repositories/{repository}/sandbox-triggers/{trigger_id}
Full replacement — all fields from Create Trigger are accepted.
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
-X PUT https://tilde.run/api/v1/organizations/my-team/repositories/my-data/sandbox-triggers/TRIGGER_ID \
-H "Content-Type: application/json" \
-d '{
"name": "Validate CSV uploads",
"conditions": [{ "type": "prefix", "prefix": "datasets/v2/" }],
"sandbox_config": {
"image": "python:3.12",
"command": ["python", "-m", "validate", "--strict"]
}
}'
Response: 200 OK — updated trigger object.
Errors: 400 (invalid input), 404 (trigger not found), 409 (name conflict)
Toggle Trigger¶
PATCH /api/v1/organizations/{organization}/repositories/{repository}/sandbox-triggers/{trigger_id}
Enable or disable a trigger without changing its configuration.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
enabled |
boolean | Yes | true to enable, false to disable |
Response: 200 OK — updated trigger object.
Delete Trigger¶
DELETE /api/v1/organizations/{organization}/repositories/{repository}/sandbox-triggers/{trigger_id}
Response: 204 No Content
Errors: 404 (trigger not found)
List Trigger Runs¶
GET /api/v1/organizations/{organization}/repositories/{repository}/sandbox-triggers/{trigger_id}/runs
Returns the history of trigger evaluations for a specific trigger.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
after |
string | Pagination cursor |
amount |
integer | Page size (default/max: 1000) |
Response: 200 OK
{
"results": [
{
"id": "run-uuid",
"trigger_id": "trigger-uuid",
"commit_id": "abc123def456",
"status": "created",
"reason": "",
"sandbox_id": "sandbox-uuid",
"matched_paths": ["datasets/"],
"created_at": "2026-03-11T10:30:00Z",
"updated_at": "2026-03-11T10:30:00Z"
}
],
"pagination": { "has_more": false, "next_offset": "", "max_per_page": 1000 }
}
Run statuses:
| Status | Description |
|---|---|
created |
Sandbox was successfully created |
skipped |
Trigger matched but sandbox was not created (reason field explains why) |
failed |
Trigger matched but sandbox creation failed |
Secrets¶
Secrets are encrypted key-value pairs that are automatically injected as environment variables into sandboxes. They are stored with AES encryption at rest. Secret values are never returned in list responses — only metadata is shown.
There are two types of secrets:
- Repository secrets — scoped to a repository. Injected into all sandboxes running in that repository.
- Agent secrets — scoped to an agent. Injected into sandboxes running as that agent, overriding repository secrets with the same key.
Key format: Must match ^[A-Za-z_][A-Za-z0-9_-]{0,255}$ — start with a letter or underscore, then letters, digits, underscores, or hyphens (max 256 characters).
Repository Secrets¶
Repository secrets are injected into all sandboxes running in the repository, regardless of which principal runs the sandbox.
RBAC: Managing secrets (create, update, list, delete) requires ManageRepositorySecrets. Reading decrypted values requires ReadRepositorySecrets.
Create or Update Repository Secret¶
PUT /api/v1/organizations/{organization}/repositories/{repository}/secrets/{secret_key}
Creates a new secret or updates the value of an existing one. On update, the original created_by metadata is preserved.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
value |
string | Yes | Secret value (max 64 KiB) |
Response: 200 OK
Errors: 400 (invalid key or empty value), 403 (insufficient permissions)
List Repository Secrets¶
GET /api/v1/organizations/{organization}/repositories/{repository}/secrets
Returns metadata for all active secrets in the repository. Values are never included.
Response: 200 OK
{
"results": [
{
"key": "API_KEY",
"created_by_type": "user",
"created_by": "user-uuid",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}
]
}
Get Repository Secret¶
GET /api/v1/organizations/{organization}/repositories/{repository}/secrets/{secret_key}
Returns the decrypted value of a single secret. Requires the ReadRepositorySecrets permission (separate from ManageRepositorySecrets).
Response: 200 OK
The response includes Cache-Control: no-store to prevent caching of secret values.
Errors: 400 (invalid key), 403 (insufficient permissions), 404 (secret not found)
Delete Repository Secret¶
DELETE /api/v1/organizations/{organization}/repositories/{repository}/secrets/{secret_key}
Soft-deletes a secret. The secret is immediately removed from future sandbox injections.
Response: 204 No Content
Errors: 400 (invalid key), 403 (insufficient permissions), 404 (secret not found)
Agent Secrets¶
Agent secrets are injected into sandboxes when the sandbox runs as that agent (via run_as). They override repository secrets with the same key.
RBAC: Managing agent secrets (create, update, list, delete) requires ManageAgentSecrets. Reading decrypted values requires ReadAgentSecrets. Both actions are scoped to agents the principal owns via the built-in AgentManager policy.
Create or Update Agent Secret¶
PUT /api/v1/organizations/{organization}/agents/{agent_name}/secrets/{secret_key}
Creates a new agent secret or updates the value of an existing one. On update, the original created_by metadata is preserved.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
value |
string | Yes | Secret value (max 64 KiB) |
Response: 200 OK
Errors: 400 (invalid key or empty value), 403 (insufficient permissions), 404 (agent not found)
List Agent Secrets¶
GET /api/v1/organizations/{organization}/agents/{agent_name}/secrets
Returns metadata for all active secrets of the agent. Values are never included.
Response: 200 OK
{
"results": [
{
"key": "OPENAI_KEY",
"created_by_type": "user",
"created_by": "user-uuid",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}
]
}
Get Agent Secret¶
GET /api/v1/organizations/{organization}/agents/{agent_name}/secrets/{secret_key}
Returns the decrypted value of a single agent secret. Requires the ReadAgentSecrets permission (separate from ManageAgentSecrets).
Response: 200 OK
The response includes Cache-Control: no-store to prevent caching of secret values.
Errors: 400 (invalid key), 403 (insufficient permissions), 404 (agent or secret not found)
Delete Agent Secret¶
DELETE /api/v1/organizations/{organization}/agents/{agent_name}/secrets/{secret_key}
Soft-deletes an agent secret. The secret is immediately removed from future sandbox injections.
Response: 204 No Content
Errors: 400 (invalid key), 403 (insufficient permissions), 404 (agent or secret not found)
Secrets in Sandboxes¶
When a sandbox is created, secrets are injected as environment variables with the following precedence (highest to lowest):
- User-supplied
env_vars— values passed in the sandbox creation request - Agent secrets — if the sandbox runs as an agent (via
run_as), the agent's secrets are injected - Repository secrets — secrets stored on the repository
At each level, a key that already exists from a higher-precedence source is not overwritten. This means user-supplied env vars override agent secrets, and agent secrets override repository secrets of the same key.
Example: A repository has secrets API_KEY=repo-key and DB_HOST=prod.db.example.com. The agent data-pipeline has secrets API_KEY=agent-key and OPENAI_KEY=sk-abc. A sandbox is created with run_as: {type: "agent", id: "..."} and env_vars: {"DB_HOST": "staging.db.example.com"}. The container sees:
| Variable | Value | Source |
|---|---|---|
DB_HOST |
staging.db.example.com |
User-supplied (overrides repo secret) |
API_KEY |
agent-key |
Agent secret (overrides repo secret) |
OPENAI_KEY |
sk-abc |
Agent secret |
Note
Agent secrets are only injected when the sandbox runs as an agent. Sandboxes running as a user or role only receive repository secrets and user-supplied env vars.
Roles¶
See the RBAC Reference for full details. Roles are non-human identities for CI/CD pipelines and automation. They authenticate via API keys.
| Method | Endpoint | Description |
|---|---|---|
POST |
/organizations/{org}/roles |
Create role |
GET |
/organizations/{org}/roles |
List roles |
GET |
/organizations/{org}/roles/{role_name} |
Get role |
DELETE |
/organizations/{org}/roles/{role_name} |
Delete role |
POST |
/organizations/{org}/roles/{role_name}/auth/keys |
Create role API key |
GET |
/organizations/{org}/roles/{role_name}/auth/keys |
List role API keys |
DELETE |
/organizations/{org}/roles/{role_name}/auth/keys/{key_id} |
Revoke role API key |
Agents¶
See the RBAC Reference for full details. Agents are non-human identities that carry metadata and support updates. They authenticate via API keys. Agents cannot create, update, or delete other agents.
| Method | Endpoint | Description |
|---|---|---|
POST |
/organizations/{org}/agents |
Create agent |
GET |
/organizations/{org}/agents |
List agents |
GET |
/organizations/{org}/agents/{agent_name} |
Get agent |
PUT |
/organizations/{org}/agents/{agent_name} |
Update agent |
DELETE |
/organizations/{org}/agents/{agent_name} |
Delete agent |
POST |
/organizations/{org}/agents/{agent_name}/auth/keys |
Create agent API key |
GET |
/organizations/{org}/agents/{agent_name}/auth/keys |
List agent API keys |
DELETE |
/organizations/{org}/agents/{agent_name}/auth/keys/{key_id} |
Revoke agent API key |
PUT |
/organizations/{org}/agents/{agent_name}/secrets/{key} |
Create or update agent secret |
GET |
/organizations/{org}/agents/{agent_name}/secrets |
List agent secrets |
GET |
/organizations/{org}/agents/{agent_name}/secrets/{key} |
Get agent secret |
DELETE |
/organizations/{org}/agents/{agent_name}/secrets/{key} |
Delete agent secret |
RBAC (Groups & Policies)¶
See the RBAC Reference for full details. Key endpoints:
| Method | Endpoint | Description |
|---|---|---|
POST |
/organizations/{org}/groups |
Create group |
GET |
/organizations/{org}/groups |
List groups |
GET |
/organizations/{org}/groups/{id} |
Get group |
PUT |
/organizations/{org}/groups/{id} |
Update group |
DELETE |
/organizations/{org}/groups/{id} |
Delete group |
POST |
/organizations/{org}/groups/{id}/members |
Add group member |
DELETE |
/organizations/{org}/groups/{id}/members |
Remove group member |
POST |
/organizations/{org}/policies |
Create policy |
GET |
/organizations/{org}/policies |
List policies |
GET |
/organizations/{org}/policies/{id} |
Get policy |
PUT |
/organizations/{org}/policies/{id} |
Update policy |
DELETE |
/organizations/{org}/policies/{id} |
Delete policy |
POST |
/organizations/{org}/policies:validate |
Validate policy |
POST |
/organizations/{org}/policies/{id}/attachments |
Attach policy to user/group |
DELETE |
/organizations/{org}/policies/{id}/attachments |
Detach policy |
GET |
/organizations/{org}/attachments |
List all attachments |
GET |
/organizations/{org}/effective-policies |
Get effective policies for current user |
Health & Metrics¶
Health Check¶
GET /health — No auth required
Response: 200 OK
Prometheus Metrics¶
GET /metrics — No auth required
Returns Prometheus-formatted metrics.