Sandbox

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/sandbox

TypeScript

npm install @tenkicloud/sandbox

Python

Requires Python 3.10+.

pip install tenki-sandbox

Authenticate

Token forms

The service accepts three token forms:

  • API key: sent as Authorization: Bearer <token> when the token starts with tk_
  • Ory session token: sent as X-Session-Token when the token starts with ory_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:

OptionDescriptionDefault
WithAuthToken(token)API authentication tokenTENKI_API_KEY env
WithBaseURL(url)Sandbox service endpointhttps://api.tenki.cloud
WithHTTPTimeout(d)HTTP timeout30s
WithHTTPClient(c)Custom *http.Clientauto-created
WithConnectClientOptions(...)Additional Connect client optionsnone

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:

ArgumentDescriptionDefault
auth_tokenAPI authentication tokenTENKI_AUTH_TOKEN env
base_urlSandbox service endpointhttps://api.tenki.cloud
cpu_cores, memory_mbVM resourcesservice defaults
allow_inbound, allow_outboundNetwork accessinbound on, outbound on
env, metadata, tagsGuest env, bookkeeping, filteringnone
snapshot_id / imageRestore source (mutually exclusive)none
volumesVolume mounts to attach at createnone
enable_opencodeStart OpenCode in the guestFalse
clone_repo_url, github_tokenClone a repo at create timenone
wait, timeoutWait for RUNNING and the wait budgetTrue, 180

Defaults

Defaults applied by Create when you do not override them:

  • inbound: false
  • outbound: true
  • cpu: 2
  • memory: 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:

  • DefaultSessionCreateTimeout
  • DefaultSnapshotCreateTimeout
  • DefaultRestoreTimeout
  • DefaultExecTimeout

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 * GiB

Errors

The Go SDK maps service errors to typed values. Common ones:

ErrorMeaning
ErrSessionNotFoundsession ID is unknown
ErrSessionExpiredsession passed its MaxDuration
ErrSessionTerminatedsession is TERMINATED
ErrInvalidStateoperation invalid for current state
ErrCommandTimeoutexec exceeded its timeout
ErrUnauthorizedbad/missing auth token
ErrPermissionDeniedauthorized but lacks permission
ErrQuotaExceededworkspace hit a resource quota
ErrPortLimitExceededtoo many exposed ports on this session
ErrInboundDisabledinbound network not enabled
ErrSSHUnavailableSSH endpoint not ready
ErrRateLimitedback off and retry
ErrVolumeNotFoundvolume ID is unknown
ErrVolumeInUsevolume already attached
ErrVolumeLimitExceededworkspace volume quota
ErrSnapshotNotFoundsnapshot ID is unknown
ErrSnapshotFailedsnapshot 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:

  • ListWorkspaceSandboxes
  • PauseSession, 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

Stick to the SDKs unless you need a raw RPC

The Go, TypeScript, and Python SDKs are the officially supported surface for Tenki Sandbox. Only drop down to raw Connect/gRPC when an RPC isn't yet wrapped by an SDK, and keep in mind that a wrapper may be added in a future release.

LinkedInProduct Hunt