Kyora Docs

Use kyora from code

Create sandboxes, run commands and move files with the TypeScript and Python SDKs.

The SDKs cover the kyora API: create sandboxes, run commands in them, move files in and out, snapshot and restore them, expose their ports and manage tokens. Both are generated from the API's OpenAPI document and behave the same: typed errors, retries that honour Retry-After, automatic idempotency keys and a deadline on every call that waits.

Install

TypeScript (Node 20.3 or later, or Bun):

npm install @kyora-sh/sdk

Python (3.10 or later), with a blocking client Kyora and an asyncio one AsyncKyora:

pip install kyora

Both read your token from KYORA_TOKEN unless you pass one, and talk to https://cloud.kyora.sh. Create a token in the kyora dashboard. For a service, use a scoped token: it can be limited to labels, regions, sizes and a number of sandboxes.

Run a command in a fresh sandbox

import { Kyora } from "@kyora-sh/sdk"

const kyora = new Kyora({ region: "eu" })

const sandbox = await kyora.sandboxes.create({ vcpus: 2, memoryMb: 4096, ttlSeconds: 900, labels: { app: "doc-agent" } })
try {
  await kyora.files.upload(sandbox.id, { "inbox/notes.txt": "hello kyora\n" })
  const result = await kyora.exec.run(sandbox.id, { cmd: ["wc", "-w", "inbox/notes.txt"], cwd: "/work" })
  console.log(result.exitCode, new TextDecoder().decode(result.stdout))
} finally {
  await kyora.sandboxes.delete(sandbox.id)
}
from kyora import Kyora

with Kyora(region="eu") as kyora:
    sandbox = kyora.sandboxes.create({"vcpus": 2, "memory_mb": 4096, "ttl_seconds": 900, "labels": {"app": "doc-agent"}})
    try:
        kyora.files.upload(sandbox.id, {"inbox/notes.txt": "hello kyora\n"})
        result = kyora.exec.run(sandbox.id, {"cmd": ["wc", "-w", "inbox/notes.txt"], "cwd": "/work"})
        print(result.exit_code, result.stdout.decode())
    finally:
        kyora.sandboxes.delete(sandbox.id)

With AsyncKyora, the same calls are awaited: async with AsyncKyora() as kyora: sandbox = await kyora.sandboxes.create(...). Python names are the TypeScript ones in snake_case (exitCode is exit_code, uploadTar is upload_tar). Durations the SDK takes or reports take seconds in Python and drop the Ms: the options timeoutMs (timeout), reattachWindowMs (reattach_window) and idleMs (idle), the retry settings (Retry(base_delay=..., max_delay=..., max_retry_after=...)), and the error's retryAfterMs (retry_after). Fields of results keep the API's unit: result.durationMs in TypeScript and result.duration_ms in Python are both milliseconds.

Errors

Every failure is a KyoraError with a code (no_capacity, quota_exceeded, name_taken, ..., or the SDK's own timeout, network_error, bad_response, stream_lost, exec_error), the HTTP status (0 when nothing answered), a class (transient, limit, conflict, not_found, auth, invalid, unavailable), whether it is retryable, and the wait kyora asked for (retryAfterMs, in Python retry_after in seconds). Branch on the code or the class, never on the message. In Python each class has its subclass: except LimitError: parks work that is over a quota.

Retries and deadlines

A call that fails with a retryable error is repeated, up to 4 tries, after the Retry-After kyora sends. Only requests that are safe to repeat are: reads, PUT, PATCH and DELETE, and creates, which carry an Idempotency-Key so a repeat never creates twice. Other writes are repeated only after a 429.

Every call takes a timeout and a deadline that cover the whole call, retries included (timeoutMs and deadline in TypeScript, timeout and deadline in Python). A Retry-After that would end past it is not slept: the call fails at once with that error, and so does one longer than maxRetryAfterMs (max_retry_after, default 30 s), even inside the deadline. That cap bounds these request retries only; reattaching a command's broken stream has its own bounds, below. Calls default to 30 s, creates to 2 minutes, and helpers that wait for a state (a snapshot ready, a restore running) to an hour. Calls that stream have no overall default bound: exec.run, exec.attach, file downloads and uploads take as long as they take unless you pass one, for example kyora.exec.run(sandbox.id, request, { timeoutMs: 600_000 }) or kyora.exec.run(sandbox.id, request, timeout=600). A create cut short may still have made its sandbox: pass your own idempotency key, and the same create with the same key answers that sandbox instead of making another.

Long-running commands

Every command runs resumable under an exec key. When the stream breaks, the command keeps running and exec.run reattaches from the bytes it already has. At its deadline, run stops waiting and fails with an ExecDetachedError carrying the command's id, key and offsets, and the command keeps running: get the rest with exec.attach, or run the same request with the same key again, from another process too, and kyora rejoins the running command instead of starting it twice. While reattaching, run waits whatever Retry-After kyora sends, however long, as long as it ends within the reattach window (reattachWindowMs, reattach_window, default 2 minutes) and the call's deadline; one that ends past either fails at once with that error, carrying what to reattach to. Cancelling the call (an AbortSignal, or cancelling the Python task) cancels the command.

Output arrives as it is written through onOutput (on_output), and in Python also as an iterator:

with kyora.exec.stream(sandbox.id, {"cmd": ["make"]}, timeout=1800) as stream:
    for chunk in stream:
        print(chunk.stream, chunk.data.decode(errors="replace"), end="")
print(stream.result.exit_code)

Files

files.upload writes files into the sandbox, under /work unless dest says otherwise. files.uploadTar takes a tar as bytes or a stream; above 64 MiB it goes up in parts. A tar.zst needs its type named, since nothing is detected: { contentType: "application/zstd" } in TypeScript, content_type="application/zstd" in Python. files.read returns one file's bytes, files.download the entries of a tree, and files.downloadTar the raw tar as it streams in.

Everything else

kyora.call(operationId, ...) sends any HTTP operation of the API, with the same errors, retries and idempotency keys. The SDKs have no helpers yet for custom domains, pages, computer use, account images and object storage; use call for those. Interactive terminals (openTerminal) and live computer streams (streamComputer) are WebSockets, which call can't open (kyora answers it 426 upgrade_required): connect to them with a WebSocket client of your own.

Each SDK's README has the full reference: TypeScript, Python.

On this page