SDK Reference
Programmatic reference for Tenki Sandbox covering installation, authentication, client options, identity discovery, OpenCode integration, and error types.
Tenki Sandbox provides three official SDKs:
- Go SDK:
github.com/TenkiCloud/tenki-sdk-go/sandbox - TypeScript SDK: published as
@tenkicloud/sandbox - Python SDK: published as
tenki-sandbox
All three SDKs wrap the same public service contract (tenki.sandbox.v1), so you get equivalent functionality regardless of which language you choose.
Install
Go
go get github.com/TenkiCloud/tenki-sdk-go/sandboxTypeScript
npm install @tenkicloud/sandboxPython
Requires Python 3.10+.
pip install tenki-sandboxAuthenticate
Token forms
The service accepts three token forms:
- API key: sent as
Authorization: Bearer <token>when the token starts withtk_ - Ory session token: sent as
X-Session-Tokenwhen the token starts withory_st_ - browser session token: sent as
Cookie: ory_kratos_session=<token>otherwise
For most external integrations, use an API key.
Resolution order
Go SDK: WithAuthToken() > TENKI_AUTH_TOKEN env var > TENKI_API_KEY env var > error.
TypeScript SDK: options.authToken > TENKI_AUTH_TOKEN env var > TENKI_API_KEY env var.
Python SDK: auth_token= > TENKI_AUTH_TOKEN env var > TENKI_API_KEY env var.
Base URL (all three SDKs): WithBaseURL() / options.baseUrl / base_url= > TENKI_API_ENDPOINT env var > legacy TENKI_API_URL env var > https://api.tenki.cloud.
Create a client
Go
import tenkisandbox "github.com/TenkiCloud/tenki-sdk-go/sandbox"
// Zero-config: reads TENKI_API_KEY and TENKI_API_URL.
client, err := tenkisandbox.New()
// Explicit:
client, err := tenkisandbox.New(
tenkisandbox.WithAuthToken("tk_your_api_key"),
tenkisandbox.WithBaseURL("https://api.example.com"),
)
defer client.Close()Useful client options:
| Option | Description | Default |
|---|---|---|
WithAuthToken(token) | API authentication token | TENKI_API_KEY env |
WithBaseURL(url) | Sandbox service endpoint | https://api.tenki.cloud |
WithHTTPTimeout(d) | HTTP timeout | 30s |
WithHTTPClient(c) | Custom *http.Client | auto-created |
WithConnectClientOptions(...) | Additional Connect client options | none |
TypeScript
import { TenkiSandbox } from "@tenkicloud/sandbox";
const sandbox = new TenkiSandbox(); // env-driven
// Or explicit:
const sandbox = new TenkiSandbox({
authToken: "tk_...",
baseUrl: "https://api.tenki.cloud",
});Python
The Python SDK exposes a low-level Client for resource APIs (volumes, snapshots) and a high-level Sandbox for driving a single session.
from tenki_sandbox import Client, Sandbox
# Resource client, env-driven (reads TENKI_API_KEY and TENKI_API_ENDPOINT).
client = Client()
# Or explicit:
client = Client(
auth_token="tk_your_api_key",
base_url="https://api.tenki.cloud",
)
# Create and drive a session directly. `Sandbox.create` waits for RUNNING by
# default and doubles as a context manager that terminates on exit.
with Sandbox.create(name="demo", cpu_cores=2, memory_mb=4096) as sb:
result = sb.exec("python3", "-c", "print('hello')")
print(result.stdout_text)Sandbox.create accepts the client options (auth_token, base_url, timeout) plus the session create arguments below.
Useful Sandbox.create / Client.create arguments:
| Argument | Description | Default |
|---|---|---|
auth_token | API authentication token | TENKI_AUTH_TOKEN env |
base_url | Sandbox service endpoint | https://api.tenki.cloud |
cpu_cores, memory_mb | VM resources | service defaults |
allow_inbound, allow_outbound | Network access | inbound on, outbound on |
env, metadata, tags | Guest env, bookkeeping, filtering | none |
snapshot_id / image | Restore source (mutually exclusive) | none |
volumes | Volume mounts to attach at create | none |
enable_opencode | Start OpenCode in the guest | False |
clone_repo_url, github_token | Clone a repo at create time | none |
wait, timeout | Wait for RUNNING and the wait budget | True, 180 |
Defaults
Defaults applied by Create when you do not override them:
inbound:falseoutbound:truecpu:2memory:4096 MB
Validation: cpu_cores 1..16, memory_mb 512..65536, volume size 1 MiB to 100 GiB.
Identity and workspaces
Use WhoAmI to discover the authenticated owner and visible workspaces. Volume APIs require a workspace ID.
identity, err := client.WhoAmI(ctx)
fmt.Printf("owner: %s/%s\n", identity.OwnerType, identity.OwnerID)
for _, ws := range identity.Workspaces {
fmt.Printf("workspace: %s (%s)\n", ws.Name, ws.ID)
}identity = client.who_am_i()
print(f"owner: {identity.owner_type}/{identity.owner_id}")
for ws in identity.workspaces:
print(f"workspace: {ws.name} ({ws.id})")OpenCode integration
Sessions can run OpenCode inside the VM for AI-driven workflows. Enable it with WithOpenCode(true) at create time, or pass WithOpenCodeProvider(...) to wire in your provider keys.
Single run
result, err := session.OpenCode.Run(
ctx,
"Review this repository and summarize risks.",
tenkisandbox.WithModel("anthropic/claude-sonnet-4-5"),
tenkisandbox.WithTimeout(5*time.Minute),
tenkisandbox.WithOnEvent(func(ev tenkisandbox.Event) {
log.Printf("[%s] %s", ev.Type, ev.Data)
}),
)const run = await session.openCode.run("Fix the failing tests", {
model: "claude-sonnet-4-20250514",
onEvent: (e) => console.log(`[${e.type}] ${e.data}`),
});
console.log(`Cost: $${run.totalCost}`);run = sb.opencode.run(
"Review this repository and summarize risks.",
model="anthropic/claude-sonnet-4-5",
timeout=300,
on_event=lambda ev: print(f"[{ev.kind}] {ev.data}"),
)
print(f"Cost: ${run.total_cost}")Long-lived instance
Start an OpenCode instance once and run many prompts against it:
instance, err := session.OpenCode.StartInstance(
ctx,
tenkisandbox.WithInstanceWorkDir("/workspace/repo"),
tenkisandbox.WithInstanceModel("anthropic/claude-sonnet-4-5"),
)
result, err := instance.Run(ctx, "Fix the failing tests")const instance = await session.openCode.startInstance({
workDir: "/workspace",
model: "claude-sonnet-4-20250514",
});
const result = await instance.run("Add unit tests");instance = sb.opencode.start_instance(
"/workspace/repo",
model="anthropic/claude-sonnet-4-5",
)
result = instance.run("Fix the failing tests")The Python instance also exposes instance.stream(prompt), which yields OpenCodeEvent objects as they arrive instead of buffering to a single result.
Abort the active OpenCode session:
err = session.OpenCode.Abort(ctx)Git helpers
The SDK exposes structured Git operations on a session.
out, err := session.Git.Clone(ctx, "https://github.com/octocat/Hello-World.git", tenkisandbox.GitCloneParams{
Directory: "/workspace/repo",
Depth: 1,
})
out, err = session.Git.Checkout(ctx, "main", tenkisandbox.GitCheckoutParams{})
out, err = session.Git.FetchPR(ctx, 123, tenkisandbox.GitFetchPRParams{
Directory: "/workspace/repo",
})await session.git.clone("https://github.com/org/repo", { depth: 1 });
await session.git.checkout("feature");
const diff = await session.git.diff({});
const log = await session.git.log({ maxCount: 10 });
await session.git.fetchPR(42, { remote: "origin" });sb.git.clone("https://github.com/octocat/Hello-World.git", depth=1, directory="/workspace/repo")
sb.git.checkout("main")
diff = sb.git.diff()
log = sb.git.log(max_count=10)
sb.git.fetch_pr(123, directory="/workspace/repo")You can also inject a GitHub token at session create time — WithGitHubToken(token) (Go), the githubToken option (TypeScript), or github_token= (Python) — so private clones work without provisioning credentials inside the guest.
Timeout constants
Go exposes named timeout constants:
DefaultSessionCreateTimeoutDefaultSnapshotCreateTimeoutDefaultRestoreTimeoutDefaultExecTimeout
Python exposes the equivalent defaults as floats (seconds) in tenki_sandbox.constants:
DEFAULT_CREATE_TIMEOUT(180)DEFAULT_EXEC_TIMEOUT(30)DEFAULT_SNAPSHOT_TIMEOUT(300)DEFAULT_VOLUME_DETACH_TIMEOUT(120)
Size constants
import { GB, GiB, KB, KiB, MB, MiB, TB, TiB } from "@tenkicloud/sandbox";
const tenGiB = 10 * GiB;The Go SDK exposes the same scale factors (tenkisandbox.GB, MiB, etc.), and the Python SDK exports them at the top level:
from tenki_sandbox import GB, GiB, KB, KiB, MB, MiB, TB, TiB
ten_gib = 10 * GiBErrors
The Go SDK maps service errors to typed values. Common ones:
| Error | Meaning |
|---|---|
ErrSessionNotFound | session ID is unknown |
ErrSessionExpired | session passed its MaxDuration |
ErrSessionTerminated | session is TERMINATED |
ErrInvalidState | operation invalid for current state |
ErrCommandTimeout | exec exceeded its timeout |
ErrUnauthorized | bad/missing auth token |
ErrPermissionDenied | authorized but lacks permission |
ErrQuotaExceeded | workspace hit a resource quota |
ErrPortLimitExceeded | too many exposed ports on this session |
ErrInboundDisabled | inbound network not enabled |
ErrSSHUnavailable | SSH endpoint not ready |
ErrRateLimited | back off and retry |
ErrVolumeNotFound | volume ID is unknown |
ErrVolumeInUse | volume already attached |
ErrVolumeLimitExceeded | workspace volume quota |
ErrSnapshotNotFound | snapshot ID is unknown |
ErrSnapshotFailed | snapshot creation failed |
if errors.Is(err, tenkisandbox.ErrSnapshotFailed) {
// inspect the snapshot record and recreate
}The TypeScript SDK exposes equivalent typed errors that all extend SandboxError:
import { CommandTimeoutError, QuotaExceededError, SessionNotFoundError } from "@tenkicloud/sandbox";
try {
await session.exec("sleep", { args: ["999"], timeoutMs: 1000 });
} catch (err) {
if (err instanceof CommandTimeoutError) {
console.log("Command timed out");
}
}The Python SDK maps service errors to typed exceptions that all subclass SandboxError:
from tenki_sandbox import CommandTimeoutError, QuotaExceededError, SessionNotFoundError
try:
sb.exec("sleep", "999", timeout=1)
except CommandTimeoutError:
print("Command timed out")Common Python error types: UnauthorizedError, PermissionDeniedError, SessionNotFoundError, SessionTerminatedError, InvalidStateError, CommandTimeoutError, CommandFailedError, QuotaExceededError, PortLimitExceededError, InboundDisabledError, RateLimitedError, VolumeNotFoundError, VolumeInUseError, VolumeSyncPendingError, SnapshotNotFoundError, SnapshotNotDurableError. A result.check() raises CommandFailedError on a non-zero exit, and RateLimitedError carries retryable = True.
Advanced API surface
The public service contract also exposes lower-level RPCs that the convenience SDKs don't wrap:
ListWorkspaceSandboxesPauseSession,ResumeSession: present in the protobuf, not always wrapped, may not be available in every deployment
If you need them, integrate directly with the protocol:
proto/tenki/sandbox/v1/sandbox.proto