Skip to content

Using the API

All Tilde API endpoints are served under /api/v1. This guide covers authentication, making requests, pagination, and error handling.

Python SDK

For Python, we recommend using the Python SDK which handles authentication, pagination, and error handling automatically. The examples below show raw HTTP requests for reference.

Base URL

https://tilde.run/api/v1

Authentication

Tilde supports two authentication methods for programmatic access. If one method is not present or its credentials are expired, the server falls through to the next without returning an error.

1. API Key

Create an API key in the Tilde web UI or via the REST API. The token is shown only once at creation time. All code examples in this documentation use API key authentication.

Create an API key in the Tilde web UI or via the REST API, then configure the SDK:

import tilde

# Set the API key (token shown once at creation)
tilde.configure(api_key="YOUR_API_TOKEN")

# All SDK calls are now authenticated
repo = tilde.repository("my-team/my-data")

You can also set the TILDE_API_KEY environment variable to avoid hardcoding credentials:

export TILDE_API_KEY="YOUR_API_TOKEN"
# Use the API key as a Bearer token in the Authorization header
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://tilde.run/api/v1/auth/me

2. Basic Auth

You can also use your email (or username) and password directly via HTTP Basic auth. This is supported for convenience but API keys are recommended for all programmatic access.

Pagination

List endpoints use keyset pagination. The response includes a pagination object:

{
  "results": [...],
  "pagination": {
    "has_more": true,
    "next_offset": "last-item-key",
    "max_per_page": 100
  }
}

To fetch the next page, pass after=<next_offset> as a query parameter. You can also set amount to control the page size (default: 100, max: 1000).

import tilde

repo = tilde.repository("my-team/my-data")

# The SDK auto-paginates -- just iterate
for commit in repo.commits.list():
    all_entries = list(commit.objects.list())
    print(f"Total objects: {len(all_entries)}")
    break
# First page (from latest committed state)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/objects?amount=100"

# Next page (use next_offset from previous response)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/objects?amount=100&after=data/file100.txt"

Error Handling

All errors return a JSON body with a human-readable message, a machine-readable code, and the request ID for debugging:

{
  "message": "repository not found",
  "code": "NOT_FOUND",
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
HTTP Status Code Meaning
400 BAD_REQUEST Invalid input (missing fields, bad format)
401 UNAUTHORIZED Missing or invalid credentials
403 FORBIDDEN Authenticated but not permitted
404 NOT_FOUND Resource does not exist
409 CONFLICT Resource already exists (duplicate name, etc.)
410 GONE Resource has been permanently removed
429 RATE_LIMITED Too many requests
500 INTERNAL Server error

Request ID

Include the request_id from error responses when reporting issues. It correlates with server-side logs for debugging.

Common Patterns

Working with Sessions

Sessions are temporary workspaces for staging changes before committing them. The typical workflow is: create a session, upload or delete objects within it, then commit the session (or roll it back if you want to discard changes).

import tilde

repo = tilde.repository("my-team/my-data")

with repo.session() as session:
    with open("new.csv", "rb") as f:
        session.objects.put("datasets/new.csv", f)
    session.commit("Add new dataset")
# rolls back automatically on exception
# Create a session
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST https://tilde.run/api/v1/organizations/my-team/repositories/my-data/sessions

# Upload a file in the session (3-step flow)
# Step 1: Get upload URL
PUT_RESP=$(curl -s -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X PUT "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/object?session_id=SESSION_ID&path=datasets/new.csv")
UPLOAD_URL=$(echo "$PUT_RESP" | jq -r .upload_url)
UPLOAD_TOKEN=$(echo "$PUT_RESP" | jq -r .upload_token)

# Step 2: Upload directly
ETAG=$(curl -s -X PUT -T new.csv -H "Content-Type: text/csv" "$UPLOAD_URL" \
  -D - -o /dev/null | grep -i '^etag:' | tr -d '\r' | cut -d' ' -f2 | tr -d '"')

# Step 3: Finalize to register in the catalog
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/object/finalize?session_id=SESSION_ID&path=datasets/new.csv" \
  -H "Content-Type: application/json" \
  -d "{\"upload_token\": \"$UPLOAD_TOKEN\", \"checksum\": \"$ETAG\", \"content_type\": \"text/csv\"}"

# Commit the session
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/sessions/SESSION_ID" \
  -H "Content-Type: application/json" \
  -d '{"message": "Add new dataset"}'

Uploading Large Files

The Tilde server is never in the data path for uploads. For very large files, use the multipart upload API to upload in parts.

import tilde

repo = tilde.repository("my-team/my-data")
with repo.session() as session:
    # The SDK automatically uses the best upload strategy:
    #  - Files under 64 MB: single upload (Put Object → upload → Finalize)
    #  - Files 64 MB and above: multipart upload (initiate → upload parts → complete)
    result = session.objects.put("data/large-file.parquet", open("large-file.parquet", "rb"))
    print(result.path, result.etag)
    session.commit("Add large dataset")
# For very large files, use the multipart upload flow.
# See the API Reference for the full curl example.

Rolling Back a Session

If you want to discard all staged changes, roll back the session instead of committing it.

session = repo.session()
session.objects.put("data/experiment.csv", b"...")
session.rollback()  # discard all staged changes
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X DELETE "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/sessions/SESSION_ID"

Approving a Session

When an agent or automated process creates a session and stages changes, a human reviewer can inspect the changes and approve them. Approval commits the session and records the approver's identity in the commit metadata.

The Python SDK handles approval blocking automatically when committing. To review and approve a session created by another principal, use the REST API:

import requests

base = "https://tilde.run/api/v1/organizations/my-team/repositories/my-data"
headers = {"Authorization": "Bearer YOUR_API_TOKEN"}

# Review the changes
changes = requests.get(f"{base}/sessions/SESSION_ID/approve", headers=headers).json()
for entry in changes["results"]:
    print(f"  {entry['diff_type']}: {entry['path']}")

# Approve and commit
resp = requests.post(
    f"{base}/sessions/SESSION_ID/approve",
    headers=headers,
    json={"message": "Approved: Add new dataset"},
)
print(f"Committed: {resp.json()['commit_id']}")
# Review the changes first
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/sessions/SESSION_ID/approve"

# Approve and commit
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/sessions/SESSION_ID/approve" \
  -H "Content-Type: application/json" \
  -d '{"message": "Approved: Add new dataset"}'

RBAC

Approving a session requires the ApproveSessionChanges permission. You can use this to separate who can create changes (agents with CreateSession + PutObject) from who can approve them (humans with ApproveSessionChanges). See the RBAC Reference for policy examples.

Reverting a Commit

If a commit introduced unwanted changes, you can revert it. Reverting creates a new commit in the repository that applies the inverse of the target commit's changes — files that were added are removed, files that were removed are restored, and modified files return to their pre-commit state. The original commit is preserved in history.

The revert succeeds only if every path affected by the target commit is unchanged in the repository's latest commit. If a subsequent commit modified one of those paths, the endpoint returns 409 Conflict.

import tilde

repo = tilde.repository("my-team/my-data")

for commit in repo.commits.list():
    revert = commit.revert(message="Undo accidental upload")
    print(f"Revert commit: {revert.id}")
    break
# Revert a commit (auto-generates the message)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/commits/COMMIT_ID/revert" \
  -H "Content-Type: application/json" \
  -d '{}'

# Revert with a custom message
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/commits/COMMIT_ID/revert" \
  -H "Content-Type: application/json" \
  -d '{"message": "Undo accidental upload"}'

Check the diff first

Before reverting, you can inspect what the commit changed using the Diff endpoint with the commit and its parent as left and right parameters.