SDK Reference
Programmatic reference for Tenki Sandbox covering installation, authentication, client options, identity discovery, OpenCode integration, and error types.
Tenki Sandbox has official SDKs for Go, TypeScript, and Python. All three wrap the same public service contract (tenki.sandbox.v1), so you get equivalent functionality whichever language you choose.
Install
npm install @tenkicloud/sandboxRequires Python 3.10+.
pip install tenkigo get github.com/LuxorLabs/tenki-sdk-go/sandboxAuthenticate
The public SDKs authenticate with Workspace API keys (tk_...), sent as Authorization: Bearer <token>. Each key
is bound to one workspace, and Sandbox requests infer that workspace automatically.
Resolution order
Every SDK resolves the auth token the same way: the token you pass explicitly (WithAuthToken() in Go, authToken in TypeScript, auth_token= in Python), then TENKI_AUTH_TOKEN, then TENKI_API_KEY.
The base URL resolves the same way: the explicit option (WithBaseURL() / baseUrl / base_url=), then TENKI_API_ENDPOINT, then the legacy TENKI_API_URL, then https://api.tenki.cloud.
Create a client
import { TenkiSandbox } from "@tenkicloud/sandbox";
const sandbox = new TenkiSandbox(); // env-driven
// Or explicit:
const sandbox = new TenkiSandbox({
authToken: "tk_...",
baseUrl: "https://api.tenki.cloud",
});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 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 also accepts the client options (auth_token, base_url, timeout), since it constructs the client for you.
import tenkisandbox "github.com/LuxorLabs/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 |
To create and drive a session, every SDK and the CLI accept the same set of options. See Create a session for the full list.
Defaults
Defaults applied by Create when you do not override them:
cpu:2memory:4096 MB
Networking is off unless you set allowInbound / allowOutbound. The CLI enables both by default.
Validation: cpu_cores 1..16, memory_mb 128..65536, volume size 1 MiB to 100 GiB.
Identity
Use WhoAmI to inspect the authenticated owner and its workspace. Normal resource calls do not need the
workspace ID; the API key already supplies that scope.
const me = await sandbox.whoAmI();
console.log(`${me.ownerType}/${me.ownerId}`);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})")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)
}OpenCode integration
Sessions can run OpenCode inside the VM for AI-driven workflows. Enable it at create time with the enableOpenCode option (WithOpenCode in Go, enable_opencode in Python). In the TypeScript and Go SDKs you can also pass a provider option (openCodeProvider / WithOpenCodeProvider) to wire in your keys. See Create a session for those options.
OpenCode then runs inside the session. The SDKs enable it at create time but do not expose an API for driving it programmatically once the session is running.
Git helpers
The SDK exposes structured Git operations on a session.
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="/home/tenki/repo")
sb.git.checkout("main")
diff = sb.git.diff()
log = sb.git.log(max_count=10)
sb.git.fetch_pr(123, directory="/home/tenki/repo")out, err := session.Git.Clone(ctx, "https://github.com/octocat/Hello-World.git", tenkisandbox.GitCloneParams{
Directory: "/home/tenki/repo",
Depth: 1,
})
out, err = session.Git.Checkout(ctx, "main", tenkisandbox.GitCheckoutParams{})
out, err = session.Git.FetchPR(ctx, 123, tenkisandbox.GitFetchPRParams{
Directory: "/home/tenki/repo",
})You can also inject a GitHub token at session create time (WithGitHubToken(token) in Go, the githubToken option in TypeScript, or github_token= in Python) so private clones work without provisioning credentials inside the guest.
Registry version pruning
Published registry versions pin their source snapshots. Delete an eligible historical version to release that pin; the version must be untagged, not the image's latest version, and unused by a share. The SDK methods take the registry image ID and snapshot ID, and return both IDs after deletion.
const deleted = await sandbox.deleteRegistryImageVersion(imageId, snapshotId);
console.log(deleted.imageId, deleted.snapshotId);deleted = client.registry.delete_version(image_id, snapshot_id)
print(deleted.image_id, deleted.snapshot_id)deleted, err := client.DeleteRegistryImageVersion(ctx, imageID, snapshotID)
fmt.Println(deleted.ImageID, deleted.SnapshotID)Deleting a latest, tagged, or shared version fails with a failed-precondition error, and the message names the rule that blocked it. Deleting the registry version does not delete the snapshot; call the snapshot deletion API separately after the pin is released.
Constants
The default operation timeouts are the same across SDKs:
| Operation | Default |
|---|---|
| Create | 180s (3m) |
| Exec | 30s |
| Snapshot create | 300s (5m) |
| Restore | 300s (5m) |
| Volume detach | 120s (2m) |
Each SDK exports them as named constants: Go DefaultSessionCreateTimeout and friends, TypeScript DEFAULT_SESSION_CREATE_TIMEOUT_MS (milliseconds), and Python DEFAULT_CREATE_TIMEOUT (seconds, in tenki_sandbox.constants).
For volume sizes, the SDKs export byte-multiplier constants (KB, MB, GB, and the binary KiB, MiB, GiB, and so on):
import { GiB } from "@tenkicloud/sandbox";
const tenGiB = 10 * GiB;from tenki import GiB
ten_gib = 10 * GiBtenGiB := 10 * tenkisandbox.GiBErrors
Every SDK surfaces service errors as typed values, sharing a common base (SandboxError in the TypeScript and Python SDKs). The common ones:
| Meaning | Go | TypeScript | Python |
|---|---|---|---|
| Session not found | ErrSessionNotFound | SessionNotFoundError | SessionNotFoundError |
| Session expired | ErrSessionExpired | SessionExpiredError | n/a |
| Session terminated | ErrSessionTerminated | SessionTerminatedError | SessionTerminatedError |
| Invalid state | ErrInvalidState | InvalidStateError | InvalidStateError |
| Command timeout | ErrCommandTimeout | CommandTimeoutError | CommandTimeoutError |
| Unauthorized | ErrUnauthorized | UnauthorizedError | UnauthorizedError |
| Permission denied | ErrPermissionDenied | PermissionDeniedError | PermissionDeniedError |
| Quota exceeded | ErrQuotaExceeded | QuotaExceededError | QuotaExceededError |
| Port limit exceeded | ErrPortLimitExceeded | PortLimitExceededError | PortLimitExceededError |
| Inbound disabled | ErrInboundDisabled | InboundDisabledError | InboundDisabledError |
| Rate limited | ErrRateLimited | RateLimitedError | RateLimitedError |
| Volume not found | ErrVolumeNotFound | VolumeNotFoundError | VolumeNotFoundError |
| Volume in use | ErrVolumeInUse | VolumeInUseError | VolumeInUseError |
| Snapshot not found | ErrSnapshotNotFound | SnapshotNotFoundError | SnapshotNotFoundError |
| Snapshot failed | ErrSnapshotFailed | SnapshotFailedError | n/a |
| Registry image not found | ErrRegistryImageNotFound | RegistryImageNotFoundError | RegistryImageNotFoundError |
The Go SDK also defines ErrSSHUnavailable and ErrVolumeLimitExceeded. Catch errors the idiomatic way in each tool:
import { CommandTimeoutError } from "@tenkicloud/sandbox";
try {
await session.exec("sleep", { args: ["999"], timeoutMs: 1000 });
} catch (err) {
if (err instanceof CommandTimeoutError) {
console.log("Command timed out");
}
}from tenki import CommandTimeoutError
try:
sb.exec("sleep", "999", timeout=1)
except CommandTimeoutError:
print("Command timed out")result.check() raises CommandFailedError on a non-zero exit, and RateLimitedError carries retryable = True. Python also defines CommandFailedError, VolumeSyncPendingError, and SnapshotNotDurableError.
if errors.Is(err, tenkisandbox.ErrSnapshotFailed) {
// inspect the snapshot record and recreate
}Advanced API surface
The public service contract also exposes lower-level RPCs that the convenience SDKs don't wrap:
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