Sandboxes
Isolated gVisor workspaces where agents run Python, bash and LibreOffice and hand back files as scanned, signed artifacts.
A sandbox is an isolated container with a persistent /workspace directory. Your code or your agent runs commands in it (Python 3, bash, LibreOffice, Node 24, the usual data and document libraries, fonts with full Romanian coverage), writes results to /workspace/out and gets them back as artifacts: scanned, hashed and downloadable through a signed link. The workspace survives between execs until the sandbox is destroyed or expires.
Two backends share this API. v2 is the default since 18 October 2026: a long-lived pod with memory snapshots (pause/resume, fork, a persistent Python kernel, preview URLs), see Sandboxes v2. v1 (Cloud Run, the workspace restored per exec, plus POST /snapshot) stays available with backend: "v1". When you omit backend and v2 is not available for your key, the gateway falls back to v1; the backend field on every sandbox object says which one you got.
Every exec is metered; exec time is billed from the wallet afterwards (see Billing), so responses carry x-codai-cost-micro-usd: 0. Every route needs a codai_ key that reaches tool level l3_sandbox.
Security model
- gVisor isolation. Every sandbox runs under gVisor with its own kernel boundary: v2 as a dedicated pod on GKE, v1 on Cloud Run (gen2). The image filesystem is read-only for your code and only
/workspaceis writable. - No network by default. With
egress: { mode: 'none' }(the default) there is no route out at all: public IPs, private ranges and the metadata server are unreachable. - Egress through an allowlisting proxy.
{ mode: 'allowlist', domains: [...] }(at most 20 host names, no IP literals, no internal suffixes) or{ mode: 'preset', preset: 'pypi' | 'npm' }lets HTTPS reach only those hosts, through the codai egress proxy, which resolves DNS itself and logs every connection. This works on both backends; the proxy opens plain tunnels and never injects credentials. - No secrets inside. No codai key, cloud credential or token is present in the sandbox; environment variables named
CODAI_*orGOOGLE_*are rejected. - Audited. Every create, exec, file transfer, artifact, snapshot and destroy is written to an append-only audit log; each exec returns a reproducibility receipt.
- Data stays in
europe-west1(x-codai-data-region).
Limits
LibreOffice converts every office format in the sandbox, for example soffice --headless --convert-to pdf --outdir /workspace/out report.docx (also .xlsx, .pptx, .odt, HTML and CSV). Every command runs under a small init that reaps background helper processes, and exit_code is the real exit status of your command.
| Limit | Default | Error |
|---|---|---|
| Running sandboxes per key | 3 | 429 sandbox_concurrency_limit |
| Running v2 sandboxes per user | 5 (all keys together) | 429 sandbox_concurrency_limit |
| Paused v2 sandboxes per user | 10 | 429 sandbox_paused_limit (on pause) |
| v2 platform capacity | shared | 503 sandbox_capacity (retry later) |
| Concurrent execs per sandbox | 1 | 409 sandbox_busy |
Lifetime (ttl_seconds) | 1 h default, 24 h max | 400 invalid_ttl |
Exec timeout (timeout_seconds) | 900 s | 400 |
| Workspace size | 2 GB | exec error event workspace_too_large |
| Exec seconds per user per day | 7 200 | 429 sandbox_daily_limit |
| File upload | 100 MB | 413 |
| Egress allowlist | 20 domains | 400 |
| stdout / stderr per exec | 1 MB each, then a truncated event | — |
Artifacts
GET /v1/sandboxes/{id}/artifacts collects every file under /workspace/out. Each artifact carries:
mime, sniffed from the content (not from the file name);sha256of the bytes;scan: the ClamAV verdict,clean,infectedorerror;url: a signed download link valid for 1 h, present only whenscanisclean(expires_atsays when it stops working).
POST /v1/sandboxes/run returns the artifacts directly in its result.
Receipts
Every finished exec (the blocking result and the final exit stream event) includes a receipt: the image digest, the template, the SHA-256 of the inputs and of the command, every output file with its SHA-256 and size, and the workspace generation before and after. Store it next to the document you produced to prove what code made it, from which inputs.
Streaming
Send Accept: text/event-stream to POST /v1/sandboxes/{id}/exec to receive data: frames as the command runs: start, then any number of stdout / stderr chunks (and truncated when a stream hits 1 MB), then exactly one exit (or error). Without that header the call blocks and returns one JSON result.
Idempotency-Key on exec and run replays the stored response of an earlier request with the same key for 24 h instead of running the command again.
Quickstart: a PDF with Romanian diacritics
One shot with POST /v1/sandboxes/run: the sandbox is created, the script runs, /workspace/out is collected and the sandbox is destroyed.
curl https://ai.codai.ro/v1/sandboxes/run \
-H "Authorization: Bearer $CODAI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: act-2026-10-16-001" \
-d @- <<'JSON'
{
"language": "python",
"timeout_seconds": 120,
"code": "import os\nfrom reportlab.lib.pagesizes import A4\nfrom reportlab.pdfbase import pdfmetrics\nfrom reportlab.pdfbase.ttfonts import TTFont\nfrom reportlab.pdfgen import canvas\n\npdfmetrics.registerFont(TTFont('DejaVu', '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf'))\nos.makedirs('/workspace/out', exist_ok=True)\nc = canvas.Canvas('/workspace/out/act.pdf', pagesize=A4)\nc.setFont('DejaVu', 14)\nc.drawString(72, 770, 'Act adițional nr. 1 la contractul de prestări servicii')\nc.drawString(72, 745, 'Părțile convin: ședința se mută în București, str. Ștefan cel Mare.')\nc.save()\nprint('ok')\n"
}
JSONThe answer is an exec result whose artifacts[0].url downloads act.pdf for one hour. The DejaVu Sans font embeds the full ă â î ș ț set, so the comma-below ș and ț render correctly.
SDKs
import { Codai } from 'codai-sdk';
const client = new Codai({ apiKey: process.env.CODAI_API_KEY! });
const sb = await client.sandboxes.create({ ttl_seconds: 1800 });
await client.sandboxes.files.put(sb.id, 'in/date.csv', 'client,suma\nSC Exemplu SRL,1200\n');
for await (const ev of client.sandboxes.execStream(sb.id, {
shell: 'python3 -c "import pandas as pd; print(pd.read_csv(\'in/date.csv\'))"',
})) {
if (ev.type === 'stdout') process.stdout.write(ev.data);
if (ev.type === 'exit') console.log('exit', ev.exit_code, ev.receipt.command_sha256);
}
const { data } = await client.sandboxes.artifacts(sb.id);
await client.sandboxes.delete(sb.id);from codai import Codai
client = Codai(api_key="...")
sb = client.sandboxes.create(ttl_seconds=1800)
client.sandboxes.files.put(sb["id"], "in/date.csv", "client,suma\nSC Exemplu SRL,1200\n")
for ev in client.sandboxes.exec_stream(sb["id"], language="python", code="print(open('in/date.csv').read())"):
if ev["type"] == "stdout":
print(ev["data"], end="")
result = client.sandboxes.run(shell="weasyprint in/page.html out/page.pdf",
inputs=[{"path": "page.html", "url": "https://example.com/page.html"}])
client.sandboxes.delete(sb["id"])The codai_sandbox chat tool
Add { "type": "codai_sandbox" } to tools in POST /v1/chat/completions (optionally with egress and template) and the gateway does the rest server-side: it gives the model sandbox_bash, sandbox_python, sandbox_edit and sandbox_files, runs them in one sandbox per x-codai-session-id (lifetime 1 h, extended by every use) and, at the end, appends the artifacts to the final message as codai_sandbox.artifacts plus a markdown list with signed links. The key still needs tool level l3_sandbox.
curl https://ai.codai.ro/v1/chat/completions \
-H "Authorization: Bearer $CODAI_API_KEY" \
-H "Content-Type: application/json" \
-H "x-codai-session-id: acte-firma-42" \
-d '{"model":"codai","tools":[{"type":"codai_sandbox"}],
"messages":[{"role":"user","content":"Fă-mi un PDF cu o hotărâre AGA pentru SC Exemplu SRL."}]}'The built-in exec_python tool now runs on the same sandbox backend; it no longer executes code inside the gateway process.
Document skills (Claude Skills compatible)
Anthropic's pre-built Agent Skills (docx, xlsx, pdf, pptx) need a first-party container that is not available on the clouds behind codai, so codai provides them on its own sandbox, for every model. Two equivalent forms:
{ "tools": [{ "type": "codai_sandbox", "skills": ["docx", "pdf"] }] }{ "container": { "skills": [{ "type": "anthropic", "skill_id": "xlsx", "version": "latest" }] } }Both work on /v1/chat/completions and /v1/messages. They enable the codai_sandbox tool and add a fixed guidance block for the requested skills (python-docx, openpyxl, reportlab/pypdf, python-pptx, weasyprint), so the model produces real files in /workspace/out and you get them as signed links. Custom skills (type: "custom") and container reuse (container.id) answer 400; use x-codai-session-id to keep one sandbox per conversation.
MCP tools
The codai MCP server (https://ai.codai.ro/mcp) exposes sandbox_create, sandbox_list, sandbox_exec, sandbox_files (list / read / write / artifacts) and sandbox_kill, plus the v2 tools sandbox_run_code, sandbox_pause, sandbox_resume, sandbox_fork and sandbox_preview (see Sandboxes v2). They call the same routes with your key, so the tool level, the limits and the metering are the same as on the REST API. sandbox_files reads return at most 256 KB inline; take bigger outputs through op: "artifacts" (signed URL + SHA-256).
Billing
Since 17 October 2026 (00:00 UTC) exec time is charged from the wallet at €0.10 per exec-hour, with the first 10 exec-hours per user per UTC month free; usage before that date is never billed. Idle running sandboxes cost nothing. Usage is settled in 10-minute buckets (wallet_ledger source sandbox_charge). If a bucket cannot be paid, new creates and execs answer 402 insufficient_balance (with unpaid_micro_eur) until a top-up settles it.
This section prices the v1 backend. Sandboxes v2 (the default) are billed differently, see v2 billing.
Sandboxes v2
Generally available to every key since 18 October 2026 and the default backend when backend is omitted. The REST surface above is shared: a sandbox picks its backend once, at create, and backend (v1 or v2) comes back on every sandbox object. If v2 is not available for your key and you did not ask for it explicitly, the create falls back to v1; with an explicit backend: "v2" it fails instead.
A v2 sandbox is a long-lived, isolated machine instead of a workspace restored per exec: processes keep running between calls, and the whole state (memory, running processes, /workspace) can be snapshotted, paused, resumed and forked. On v2:
- Egress works as on v1:
none(default), apypi/npmpreset or an allowlist of up to 20 domains, through the codai egress proxy (plain tunnels, no credential injection). Forks inherit the parent's egress. - Toolchain: Python 3, bash, the document libraries and Node 24 with npm in every template.
inputson exec andGET /artifacts(ClamAV-scanned, SHA-256, signed URLs) behave exactly as on v1.- v1 only:
POST /v1/sandboxes/{id}/snapshotanswers409 not_supported_on_v2; use pause/fork instead.
v2 limits
| Limit | Value | Error |
|---|---|---|
| Running sandboxes per key | 3 | 429 sandbox_concurrency_limit |
| Running v2 sandboxes per user | 5, across all your keys | 429 sandbox_concurrency_limit |
| Paused v2 sandboxes per user | 10 | 429 sandbox_paused_limit on pause |
| Fork children per call | 5 | 400 invalid_count |
| Platform capacity | shared pool | 503 sandbox_capacity, retry later |
503 sandbox_capacity is transient: retry with backoff, or create with backend: "v1".
Templates
template | What is inside |
|---|---|
codai-base | The default image: Python 3, bash, Node 24 + npm and the document libraries. |
brivio-acte | codai-base plus LibreOffice, prepared for office documents and started warm. |
desktop | A Linux desktop reachable through noVNC on port 6080 (open it with a preview URL). |
When a template has a warm snapshot, the create answer carries restored: true.
Persistent kernel: run_code
POST /v1/sandboxes/{id}/run_code with { "code": "...", "timeout_seconds": 60 } runs a Python cell in one long-lived kernel. Globals survive between calls, across pause/resume and into forks. The answer is { stdout, stderr, result, error, duration_ms, execution_count, kernel_restarted }: result is the repr of the last expression (at most 64 KB), error is { name, value, traceback } for an uncaught exception. A cell past its timeout restarts the kernel (kernel_restarted: true, state lost). POST /v1/sandboxes/{id}/kernel/reset restarts it on purpose; files are kept.
Pause and resume
POST /v1/sandboxes/{id}/pause snapshots the sandbox and stops it; its status becomes paused. You can keep at most 10 paused v2 sandboxes (429 sandbox_paused_limit); delete old ones to free a place. Resuming counts against the running caps again. POST /v1/sandboxes/{id}/resume brings it back and answers the sandbox plus restored and ready_ms. restored: true means memory and processes came back exactly as they were; restored: false means a cold start from the saved workspace (files intact, kernel state lost). codai measures this inside the sandbox; it is never assumed.
Fork
POST /v1/sandboxes/{id}/fork with { "count": 1..5, "ttl_seconds"?: n } snapshots a running sandbox (it keeps running) and starts count copies from that snapshot. Each copy is a full sandbox with its own id, workspace and kernel state, and is billed like any other. The answer is { object: "list", parent_id, data: [{ id, restored, ready_ms, error? }] }: per copy, restored: false means it started cold and error means it failed and is not running. A fork may not take a key above 6 live sandboxes, nor you above 5 running v2 sandboxes (429 sandbox_concurrency_limit).
Preview URLs
POST /v1/sandboxes/{id}/preview with { "port": 3000, "ttl_seconds": 900 } answers { port, url, expires_at }: a signed HTTPS URL on *.sbx.codai.ro that forwards to that port inside the sandbox (a dev server, or noVNC on 6080 in desktop). Ports 1024–65535 except 8080; validity 60 s to 1 h (default 15 min). The URL needs no key: anyone holding it reaches the port until it expires, so share it only with the people who should see the app.
const sb = await client.sandboxes.create({
template: 'codai-base',
egress: { mode: 'preset', preset: 'pypi' },
}); // v2 by default
await client.sandboxes.runCode(sb.id, {
code: 'import pandas as pd\ndf = pd.DataFrame({"a": [1, 2]})',
});
const r = await client.sandboxes.runCode(sb.id, { code: 'df["a"].sum()' }); // r.result === '3'
const { data } = await client.sandboxes.fork(sb.id, { count: 3 });
await client.sandboxes.pause(sb.id);
const back = await client.sandboxes.resume(sb.id); // back.restored, back.ready_mssb = client.sandboxes.create(backend="v2", template="desktop")
p = client.sandboxes.preview(sb["id"], 6080, ttl_seconds=600)
print(p["url"], p["expires_at"])v2 billing
- Running: €0.10 per wall-clock hour while the sandbox exists (busy or idle), with 5 free hours per user per UTC month (separate from the v1 exec-hours above).
- Paused: only the snapshot is stored, at €0.05 per GB-month, metered hourly.
- Charges are settled from the wallet like v1 (
402 insufficient_balancewhen a bucket cannot be paid).
bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
x-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128application/json- body
backend?stringExecution backend: v2 (default: long-lived pod with memory snapshots, pause/resume, fork, persistent kernel, preview URLs, egress through the codai proxy, Node 24 + npm) or v1 (Cloud Run, workspace restored per exec, POST /snapshot). When omitted and v2 is not available for the key, the sandbox is created on v1; an explicit v2 fails instead.
"v2""v1""v2"template?stringImage template. v1: codai-base only. v2: codai-base, brivio-acte (office toolchain warmed up) or desktop (a Linux desktop over noVNC on port 6080).
"codai-base""codai-base""brivio-acte""desktop"ttl_seconds?integerLifetime; the sandbox expires afterwards (sliding for chat sessions). At most 86400 on v1 and 604800 on v2.
60 <= value <= 6048003600egress?||Outbound network, on both backends. none (default) has no network at all; allowlist lets HTTPS reach at most 20 domains through the codai egress proxy (no IP literals, no internal hosts); preset opens a curated list (pypi, npm). The proxy opens plain tunnels and never injects credentials; v2 forks inherit the parent policy.
metadata?Your own labels, returned as-is.
The new sandbox.
application/json- response
id*stringuuidobject*"sandbox""sandbox"status*string"running""paused""stopped""expired""destroyed"backend*stringBackend chosen at create time.
"v1""v2"restored?booleanOnly on create of a v2 sandbox: true when it started from a warm template snapshot.
template*stringegress*||Outbound network, on both backends. none (default) has no network at all; allowlist lets HTTPS reach at most 20 domains through the codai egress proxy (no IP literals, no internal hosts); preset opens a curated list (pypi, npm). The proxy opens plain tunnels and never injects credentials; v2 forks inherit the parent policy.
created_at*stringdate-timeexpires_at*stringdate-timelast_exec_at*|date-timemetadata*usage*curl -X POST 'https://ai.codai.ro/v1/sandboxes' \ -H 'Content-Type: application/json' \ -d '{}'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "object": "sandbox", "status": "running", "backend": "v1", "restored": true, "template": "string", "egress": { "mode": "none" }, "created_at": "2019-08-24T14:15:22Z", "expires_at": "2019-08-24T14:15:22Z", "last_exec_at": "2019-08-24T14:15:22Z", "metadata": { "property1": "string", "property2": "string" }, "usage": { "exec_count": 0, "exec_seconds": 0 }}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
status?stringOnly sandboxes in this status.
"running""paused""stopped""expired""destroyed"x-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128Your sandboxes.
application/json- response
object?"list""list"data*array<>curl -X GET 'https://ai.codai.ro/v1/sandboxes'{ "object": "list", "data": [ { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "object": "sandbox", "status": "running", "backend": "v1", "restored": true, "template": "string", "egress": { "mode": "none" }, "created_at": "2019-08-24T14:15:22Z", "expires_at": "2019-08-24T14:15:22Z", "last_exec_at": "2019-08-24T14:15:22Z", "metadata": { "property1": "string", "property2": "string" }, "usage": { "exec_count": 0, "exec_seconds": 0 } } ]}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128The sandbox.
application/json- response
id*stringuuidobject*"sandbox""sandbox"status*string"running""paused""stopped""expired""destroyed"backend*stringBackend chosen at create time.
"v1""v2"restored?booleanOnly on create of a v2 sandbox: true when it started from a warm template snapshot.
template*stringegress*||Outbound network, on both backends. none (default) has no network at all; allowlist lets HTTPS reach at most 20 domains through the codai egress proxy (no IP literals, no internal hosts); preset opens a curated list (pypi, npm). The proxy opens plain tunnels and never injects credentials; v2 forks inherit the parent policy.
created_at*stringdate-timeexpires_at*stringdate-timelast_exec_at*|date-timemetadata*usage*curl -X GET 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "object": "sandbox", "status": "running", "backend": "v1", "restored": true, "template": "string", "egress": { "mode": "none" }, "created_at": "2019-08-24T14:15:22Z", "expires_at": "2019-08-24T14:15:22Z", "last_exec_at": "2019-08-24T14:15:22Z", "metadata": { "property1": "string", "property2": "string" }, "usage": { "exec_count": 0, "exec_seconds": 0 }}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128Idempotency-Key?stringReplays the stored response of an earlier request with the same key for 24 h instead of running it again.
length <= 255application/json- body
Exactly one of cmd, shell or language + code.
cmd?array<string>Command as argv (no shell).
items <= 256shell?stringA shell line run with /bin/bash -lc.
length <= 65536language?stringRun code with this interpreter.
"python""bash"code?stringSource code for language.
length <= 1048576env?Environment variables (at most 64; names starting with CODAI_ or GOOGLE_ are rejected).
cwd?stringWorking directory inside /workspace.
length <= 1024timeout_seconds?integerWall-clock limit for this exec.
1 <= value <= 900120stdin?stringStandard input.
length <= 1048576inputs?array<>Files written read-only under /workspace/in before the command runs.
items <= 50JSON result, or an SSE stream when the request sent Accept: text/event-stream.
- response
exit_code*|The command's own exit status; 128 + n when it was ended by signal n. null only when the exec timed out (timed_out: true) or the process could not be started.
stdout*stringStandard output (at most 1 MB).
stderr*stringStandard error (at most 1 MB).
timed_out*booleanduration_ms*integerartifacts?array<>Present on /v1/sandboxes/run.
receipt*What ran and what it produced: image digest, hashes of the inputs and the command, every output file with its SHA-256, and the workspace generations before and after.
curl -X POST 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08/exec' \ -H 'Content-Type: application/json' \ -d '{}'{ "exit_code": 0, "stdout": "string", "stderr": "string", "timed_out": true, "duration_ms": 0, "artifacts": [ { "id": "string", "path": "string", "bytes": 0, "mime": "string", "sha256": "string", "scan": "clean", "url": "string", "expires_at": "2019-08-24T14:15:22Z" } ], "receipt": { "image_digest": "string", "template": "string", "input_sha256": "string", "command_sha256": "string", "outputs": [ { "path": "string", "sha256": "string", "bytes": 0 } ], "generation_before": 0, "generation_after": 0 }}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidpath*stringFile path relative to /workspace (slashes allowed, each segment URL-encoded).
length <= 1024x-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128body*fileapplication/octet-streambinaryWritten: size, SHA-256 and new generation.
application/json- response
path*stringbytes*integersha256*stringgeneration*integercurl -X PUT 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08/files/string' \ -H 'Content-Type: application/octet-stream' \ -d 'string'{ "path": "string", "bytes": 0, "sha256": "string", "generation": 0}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidpath*stringFile path relative to /workspace (slashes allowed, each segment URL-encoded).
length <= 1024x-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128File content.
response?fileapplication/octet-streambinarycurl -X GET 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08/files/string'"string"bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidpath?stringDirectory relative to /workspace.
length <= 1024x-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128Directory entries.
application/json- response
entries*array<>curl -X GET 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08/files'{ "entries": [ { "path": "string", "bytes": 0, "type": "file", "mtime": "2019-08-24T14:15:22Z" } ]}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128The artifacts.
application/json- response
data*array<>curl -X GET 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08/artifacts'{ "data": [ { "id": "string", "path": "string", "bytes": 0, "mime": "string", "sha256": "string", "scan": "clean", "url": "string", "expires_at": "2019-08-24T14:15:22Z" } ]}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128application/json- body
name?stringOptional snapshot name.
length <= 128The snapshot.
application/json- response
object*stringStorage object name.
bytes*integersha256*stringcurl -X POST 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08/snapshot' \ -H 'Content-Type: application/json' \ -d '{}'{ "object": "string", "bytes": 0, "sha256": "string"}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128Destroyed.
curl -X DELETE 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08'bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
x-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128Idempotency-Key?stringReplays the stored response of an earlier request with the same key for 24 h instead of running it again.
length <= 255application/json- body
The fields of SandboxCreate plus those of SandboxExecRequest.
template?"codai-base"Image template.
"codai-base""codai-base"ttl_seconds?integerLifetime; the sandbox expires afterwards (sliding for chat sessions).
60 <= value <= 864003600egress?||Outbound network, on both backends. none (default) has no network at all; allowlist lets HTTPS reach at most 20 domains through the codai egress proxy (no IP literals, no internal hosts); preset opens a curated list (pypi, npm). The proxy opens plain tunnels and never injects credentials; v2 forks inherit the parent policy.
metadata?Your own labels, returned as-is.
cmd?array<string>Command as argv (no shell).
items <= 256shell?stringA shell line run with /bin/bash -lc.
length <= 65536language?stringRun code with this interpreter.
"python""bash"code?stringSource code for language.
length <= 1048576env?Environment variables (at most 64; names starting with CODAI_ or GOOGLE_ are rejected).
cwd?stringWorking directory inside /workspace.
length <= 1024timeout_seconds?integerWall-clock limit for this exec.
1 <= value <= 900120stdin?stringStandard input.
length <= 1048576inputs?array<>Files written read-only under /workspace/in before the command runs.
items <= 50Exit code, output, artifacts and receipt.
application/json- response
exit_code*|The command's own exit status; 128 + n when it was ended by signal n. null only when the exec timed out (timed_out: true) or the process could not be started.
stdout*stringStandard output (at most 1 MB).
stderr*stringStandard error (at most 1 MB).
timed_out*booleanduration_ms*integerartifacts?array<>Present on /v1/sandboxes/run.
receipt*What ran and what it produced: image digest, hashes of the inputs and the command, every output file with its SHA-256, and the workspace generations before and after.
curl -X POST 'https://ai.codai.ro/v1/sandboxes/run' \ -H 'Content-Type: application/json' \ -d '{}'{ "exit_code": 0, "stdout": "string", "stderr": "string", "timed_out": true, "duration_ms": 0, "artifacts": [ { "id": "string", "path": "string", "bytes": 0, "mime": "string", "sha256": "string", "scan": "clean", "url": "string", "expires_at": "2019-08-24T14:15:22Z" } ], "receipt": { "image_digest": "string", "template": "string", "input_sha256": "string", "command_sha256": "string", "outputs": [ { "path": "string", "sha256": "string", "bytes": 0 } ], "generation_before": 0, "generation_after": 0 }}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128application/json- body
code*stringPython source of the cell.
1 <= length <= 1000000timeout_seconds?integerSeconds before the kernel is restarted (default 60, max 900).
1 <= value <= 90060The cell result.
application/json- response
stdout*stringText printed to stdout during the cell.
stderr*stringText printed to stderr during the cell.
result*|repr of the last expression, or null.
error*|The uncaught exception, or null.
duration_ms*integerWall-clock milliseconds.
execution_count*integerKernel execution counter.
kernel_restarted*booleanTrue when the kernel was restarted (timeout or crash) and its state is lost.
curl -X POST 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08/run_code' \ -H 'Content-Type: application/json' \ -d '{ "code": "string"}'{ "stdout": "string", "stderr": "string", "result": "string", "error": { "name": "string", "value": "string", "traceback": "string" }, "duration_ms": 0, "execution_count": 0, "kernel_restarted": true}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128The kernel was restarted.
application/json- response
ok*truetruecurl -X POST 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08/kernel/reset'{ "ok": true}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128The paused sandbox.
application/json- response
id*stringuuidobject*"sandbox""sandbox"status*string"running""paused""stopped""expired""destroyed"backend*stringBackend chosen at create time.
"v1""v2"restored?booleanOnly on create of a v2 sandbox: true when it started from a warm template snapshot.
template*stringegress*||Outbound network, on both backends. none (default) has no network at all; allowlist lets HTTPS reach at most 20 domains through the codai egress proxy (no IP literals, no internal hosts); preset opens a curated list (pypi, npm). The proxy opens plain tunnels and never injects credentials; v2 forks inherit the parent policy.
created_at*stringdate-timeexpires_at*stringdate-timelast_exec_at*|date-timemetadata*usage*curl -X POST 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08/pause'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "object": "sandbox", "status": "running", "backend": "v1", "restored": true, "template": "string", "egress": { "mode": "none" }, "created_at": "2019-08-24T14:15:22Z", "expires_at": "2019-08-24T14:15:22Z", "last_exec_at": "2019-08-24T14:15:22Z", "metadata": { "property1": "string", "property2": "string" }, "usage": { "exec_count": 0, "exec_seconds": 0 }}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128The resumed sandbox.
application/json- response
id*stringuuidobject*"sandbox""sandbox"status*string"running""paused""stopped""expired""destroyed"backend*stringBackend chosen at create time.
"v1""v2"restored*booleanTrue when the memory state was restored, false on a cold start.
template*stringegress*||Outbound network, on both backends. none (default) has no network at all; allowlist lets HTTPS reach at most 20 domains through the codai egress proxy (no IP literals, no internal hosts); preset opens a curated list (pypi, npm). The proxy opens plain tunnels and never injects credentials; v2 forks inherit the parent policy.
created_at*stringdate-timeexpires_at*stringdate-timelast_exec_at*|date-timemetadata*usage*ready_ms*integerMilliseconds until the sandbox answered.
curl -X POST 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08/resume'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "object": "sandbox", "status": "running", "backend": "v1", "template": "string", "egress": { "mode": "none" }, "created_at": "2019-08-24T14:15:22Z", "expires_at": "2019-08-24T14:15:22Z", "last_exec_at": "2019-08-24T14:15:22Z", "metadata": { "property1": "string", "property2": "string" }, "usage": { "exec_count": 0, "exec_seconds": 0 }, "restored": true, "ready_ms": 0}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128application/json- body
count*integerHow many children to start (1–5).
1 <= value <= 5ttl_seconds?integerLifetime of each child in seconds (default: what is left of the parent).
60 <= value <= 604800The children.
application/json- response
object*"list""list"parent_id*stringuuiddata*array<>curl -X POST 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08/fork' \ -H 'Content-Type: application/json' \ -d '{ "count": 1}'{ "object": "list", "parent_id": "1c6ca187-e61f-4301-8dcb-0e9749e89eef", "data": [ { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "restored": true, "ready_ms": 0, "error": { "code": "string", "message": "string" } } ]}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringSandbox id (uuid).
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128application/json- body
port*integerPort inside the sandbox (1024–65535, not 8080).
1024 <= value <= 65535ttl_seconds?integerValidity in seconds (60–3600, default 900).
60 <= value <= 3600900The signed preview URL.
application/json- response
port*integerurl*stringSigned HTTPS URL; anyone holding it reaches the port until it expires.
uriexpires_at*stringdate-timecurl -X POST 'https://ai.codai.ro/v1/sandboxes/497f6eca-6276-4993-bfeb-53cbbbba6f08/preview' \ -H 'Content-Type: application/json' \ -d '{ "port": 1024}'{ "port": 0, "url": "http://example.com", "expires_at": "2019-08-24T14:15:22Z"}