T0 API documentation

Hosted, sandboxed agent sessions over HTTP. Create a session, push files into it, send it messages, and stream the agent working — in an isolated Linux workspace that is still there on the next message.

Base URL
https://api.t0.sikasio.com/v1
Auth
Authorization: Bearer t0k_live_YOUR_KEY
Streaming
Server-Sent Events, resumable via Last-Event-ID, plus a bidirectional WebSocket
Models
light · flash · core · max — model access included with your plan
Free plan
60 credits/month (1 h of awake session time), no card required
Status
status.t0.sikasio.com

A session is an isolated sandbox, a persistent workspace and one long-lived agent process. A turn is one message in and one response out, streamed as ordered, sequence-numbered events. Files written by the agent, packages it installed and git state all survive between messages, so message two builds on message one.

T0 sells the runtime — isolation, persistence, orchestration and latency — with model access included: sessions run on T0's own Claude Code account behind T0's gateway, you never handle a model credential, and your plan's credits are the only meter.

Get your free API key →

Getting your API key

  1. Create an account at /signup — email and password, no card.
  2. Verify your email with the code we send you.
  3. Copy your API key. It is shown once, right after verification. Store it somewhere safe; you can always issue a fresh one from the dashboard.
  4. Nothing to bring: model access is included — sessions run on T0's own Claude Code account behind T0's gateway, and your plan's credits are the only limit.
  5. Make your first call — see the quickstart below.

Already have an account? Sign in. Want to try the API without writing a client first? The playground is built on nothing but the public API and your own key.

Authentication

Every /v1 request carries your key as a bearer token:

Authorization: Bearer t0k_live_YOUR_KEY

The key is the tenant boundary: it identifies the account, authorizes the request, and is what usage is metered against. Every session, event and file belongs to the account behind the key, and a request for another account’s session is a 404, never a redirect or a hint.

Keys are stored as an indexed prefix plus an HMAC-SHA256 digest — never in plaintext — and compared in constant time. Revoking a key from the dashboard takes effect within seconds, everywhere.

Keep keys server-side. The API host sets no cookies and is a separate origin from this portal, so there is no session to borrow and no CSRF surface — but a key shipped in a browser bundle is a key you have given away. Call T0 from a server you control, or proxy it.

A missing, malformed, revoked or unknown key returns 401 invalid_key. A valid key used against another account’s resource returns 403 forbidden.

Plans & quotas

A credit is one minute of awake session time. Sleeping sessions cost nothing, so a session you leave open overnight and wake in the morning is billed for the minutes it worked, not the hours it waited.

Every plan, side by side
 FreeStarterPro
Price$0$19/mo$59/mo
Credits / monthone credit = one minute awake60 (1 h)600 (10 h)2,400 (40 h)
Daily capcredits per UTC day20120360
Concurrent sessions1310
Concurrent runsone-shot /v1/runs, counted apart21030
Workspace storagedurable, per account400 MB5 GB20 GB
Live disk per sessiontechnical ceiling5 GB10 GB20 GB
Retentionafter a session ends7 days30 days90 days
Sandboxmemory / CPU1 GB / 1 CPU2 GB / 1.5 CPU4 GB / 2 CPU
Past the allowancecredits, on every plan$0.04 / min$0.04 / min$0.04 / min
Top-upscredits never expire$10 → 250 · $25 → 625$10 → 250 · $25 → 625$10 → 250 · $25 → 625

Metered: awake session-seconds, peak workspace bytes and turns. Token counts are recorded and shown to you for your own visibility — model access is included with your plan and never billed separately. Enforced: concurrent sessions, monthly and daily credits, workspace size, retention window and request rate.

Credits: past the allowance

Every account can also hold a prepaid credit balance, on any plan. It does nothing until the monthly allowance is spent; from then on it pays for the minutes the allowance would have refused, at $0.04 / min — the same rate whichever model tier the session runs. Nothing changes plan, and there is nothing to switch back from.

$10 buys 250 credits (4 h 10 min) · $25 buys 625 credits (10 h 25 min). Credits never expire, and they are spent by the second: a five-second turn costs five seconds of credit, not a rounded-up minute.

The daily cap lifts while you are on credits — an allowance needs rationing, a balance you are paying for does not — but it does not disappear: eight hours of awake time in one UTC day is the ceiling, as a guard against a loop rather than against you.

Never a surprise bill. Past the monthly allowance, an account with credits keeps working and spends them; an account without gets 429 quota_exceeded naming the limit, the next plan up and the option of a top-up. Nothing is ever billed beyond the credits you bought, and sessions already running are not killed mid-turn.

Full breakdown on the pricing page. Live figures for your own account come from GET /v1/usage.

Quickstart

Create a session, put a file in it, send a message, and read the result back out. Session creation is a claim against a pool of pre-booted containers, so it is bookkeeping rather than a boot.

From zero to a finished turn
# 1. create a session — comes from the warm pool, ready in well under a second
SESSION=$(curl -s -X POST https://api.t0.sikasio.com/v1/sessions \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"quickstart","model":"core"}' | jq -r .id)

# 2. push a file into its workspace
curl -X PUT https://api.t0.sikasio.com/v1/sessions/$SESSION/files/notes.md \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  --data-binary @notes.md

# 3. stream the events, then send a message from another shell
curl -N https://api.t0.sikasio.com/v1/sessions/$SESSION/events \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -H "Accept: text/event-stream" &

curl -X POST https://api.t0.sikasio.com/v1/sessions/$SESSION/messages \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content":"Summarise notes.md into summary.md, then run the tests."}'

# 4. take the result back out — works even after the session has gone to sleep
curl -s https://api.t0.sikasio.com/v1/sessions/$SESSION/files/summary.md \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -o summary.md

One-shot runs

One prompt, one answer, one HTTP call. POST /v1/runs creates a session, sends your message, waits for the turn to finish and hands the container back — so a caller who wants a question answered never has to learn what a session is.

Use it for the work that has no second turn: classification, extraction, drafting, summarising, anything where you have a prompt and a shape you want back. Use a session when the agent needs a workspace, tools, or memory of what it just did.

With tools off, billing starts at the first byte of the answer: the container claim, agent start-up and prompt plumbing before it are T0's overhead, and you do not pay for it. Each turn bills a minimum of one second — sixty quick runs for one credit. The seconds in the response is exactly what was billed.

Run a prompt
curl -X POST https://api.t0.sikasio.com/v1/runs \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Draft a two-line product blurb for a running shoe.",
    "model": "light"
  }'

Response — 200

{
  "id": "run_9f21b0c4e7a4",
  "model": "light",
  "output": "Built for the long way round...",
  "usage": { "tokens_in": 212, "tokens_out": 48 },
  "seconds": 4,
  "repaired": false
}

Parameters

FieldTypeDefaultNotes
input requiredstring—The prompt. One message, not a conversation.
modelstringcoreSame tiers as a session, free to pick on any plan. light is the usual choice here — it is the tier built to answer rather than prepare.
toolsoff | alloffOff by default, and that is most of why a run is cheap: an agent with a toolkit reads the workspace and considers writing a file before it answers, and you are billed for the seconds it spends deciding not to. Send all when the answer genuinely needs the machine.
response_formatobject{ "type": "text" }json_object to require a JSON value, or json_schema with a schema to require a particular one. See below.
timeoutinteger (s)1205 to 300. The request is held open until the turn finishes; past this it answers 504 timeout and the container is released either way.

Structured output

Ask for a shape and you get it parsed, in output_json, beside the raw output. If the first answer is not usable — not JSON, or JSON of the wrong shape — T0 sends it back once with the specific problems named, and tells you it did so in repaired. That repair is a second turn and is billed like one. A second failure answers 503 rather than a third attempt.

Ask for a schema
curl -X POST https://api.t0.sikasio.com/v1/runs \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "One leg exercise for a beginner.",
    "response_format": {
      "type": "json_schema",
      "schema": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "sets": { "type": "integer" },
          "reps": { "type": "integer" }
        },
        "required": ["name", "sets", "reps"],
        "additionalProperties": false
      }
    }
  }'

Response — 200

{
  "id": "run_9f21b0c4e7a4",
  "model": "light",
  "output": "{"name":"Glute bridge","sets":3,"reps":12}",
  "output_json": { "name": "Glute bridge", "sets": 3, "reps": 12 },
  "usage": { "tokens_in": 268, "tokens_out": 31 },
  "seconds": 3,
  "repaired": false
}

Which schema keywords are enforced. Your whole schema is given to the model, but T0 checks the answer against type, properties, required, items, enum, const, nullable, minimum/maximum on numbers and additionalProperties: false. Anything else — pattern, minLength, format — is asked for and not verified. Said plainly here rather than left to be discovered: a checker that claimed more than it does is the version of this that loses your data.

Limits

Runs are counted against concurrent runs, not concurrent sessions. A run holds a container for the seconds of one turn, a session holds one for as long as you keep it — so a burst of runs never locks you out of the sandboxes you are keeping open, and a full session list never blocks a run. Over the ceiling answers 429 rate_limited with details.limit: "concurrent_runs" and a Retry-After — and only after waiting for a slot for you first. Deliberately not quota_exceeded: nothing is exhausted, another run just finished ahead of yours, and the two call for opposite behaviour from a client.

Sandbox capacity is separate again. A run claims a container from the warm pool, and a burst that briefly outruns the pool waits rather than failing — up to fifteen seconds, and never past your own timeout. Only if it is still empty then does the call answer 409 session_not_ready with a Retry-After.

Billing is identical to a session's: the seconds the turn was awake, to the second, off the same credits. Nothing is retained — the workspace is destroyed with the run, and the run does not appear in GET /v1/sessions.

Sessions

A session owns a container, a workspace volume and a live agent process. The process stays alive for the life of the session, which is what keeps follow-up turns fast.

States

StateMeaning
readyAwake and idle. Send it a message.
workingA turn is in flight; events are streaming.
sleepingIdle past idle_timeout: the container is stopped, the workspace is kept, and nothing is being metered. The next message wakes it.
expiredPast its ttl or its plan’s retention window. The workspace has been deleted.
failedThe sandbox could not be started or the agent died unrecoverably.

Create a session

POST/v1/sessionsrequires API key
FieldTypeDefaultNotes
namestring—Your own label, returned on every read. Purely for your bookkeeping.
modelstringcorelight is the general-purpose everyday choice: the fastest model, and an agent that answers rather than prepares — it still reads, writes and runs whatever you ask it to, it just does not load a reference pack or build a file to answer a question. It is also the chattiest, which matters because a credit is a minute: on a conversational prompt it can finish later than core despite generating faster. Ask for response_format and that stops — T0 tells the sandbox the answer is machine-read, and the tier drops the presentation.flash is the same model with the full toolkit, core is more thorough, max is for the hardest tasks. Fixed for the life of the session, and free to pick on every plan — a minute costs the same whichever tier runs it.
idle_timeoutinteger (s)600Seconds of inactivity before the session sleeps. Sleeping is free, so a short timeout costs you nothing but a resume.
ttlinteger (s)plan retentionHard lifetime. At expiry the workspace is deleted, whatever state the session is in.
envobject—Environment variables for the workspace. No model credential belongs here — model access comes through T0's gateway.
Create a session
curl -X POST https://api.t0.sikasio.com/v1/sessions \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "invoice-parser",
    "model": "core",
    "idle_timeout": 600,
    "ttl": 86400,
    "env": { "NODE_ENV": "test" }
  }'

Response — 201

{
  "id": "sess_7f3a9c2e",
  "name": "invoice-parser",
  "state": "ready",
  "model": "core",
  "stream_url": "https://api.t0.sikasio.com/v1/sessions/sess_7f3a9c2e/events",
  "idle_timeout": 600,
  "created_at": "2026-08-17T09:14:02.117Z",
  "last_active_at": "2026-08-17T09:14:02.117Z",
  "expires_at": "2026-08-18T09:14:02.117Z"
}

List sessions

GET/v1/sessions?limit&cursorrequires API key

Cursor-paginated, newest first. limit defaults to 20; follow next_cursor until it comes back null.

List sessions
curl -s "https://api.t0.sikasio.com/v1/sessions?limit=20" -H "Authorization: Bearer t0k_live_YOUR_KEY"

# next page
curl -s "https://api.t0.sikasio.com/v1/sessions?limit=20&cursor=c_9f21b0" -H "Authorization: Bearer t0k_live_YOUR_KEY"
Response — 200
{
  "data": [
    { "id": "sess_7f3a9c2e", "name": "invoice-parser", "state": "working",  "workspace_bytes": 41235968 },
    { "id": "sess_2b8d41af", "name": "nightly-refactor", "state": "sleeping", "workspace_bytes": 8912384 }
  ],
  "next_cursor": "c_9f21b0"
}

Retrieve a session

GET/v1/sessions/:idrequires API key

Reading a sleeping session does not wake it — this is the cheap way to poll state and workspace size.

Retrieve a session
curl -s https://api.t0.sikasio.com/v1/sessions/sess_7f3a9c2e -H "Authorization: Bearer t0k_live_YOUR_KEY"

Delete a session

DELETE/v1/sessions/:idrequires API key

Stops the container and deletes the workspace immediately, including its snapshot. Returns 204. Download anything you want to keep first — this is not reversible.

Delete a session
curl -X DELETE https://api.t0.sikasio.com/v1/sessions/sess_7f3a9c2e -H "Authorization: Bearer t0k_live_YOUR_KEY"

Interrupt a turn

POST/v1/sessions/:id/interruptrequires API key

Stops the turn in flight without killing the session: the agent process stays alive, the workspace keeps whatever was written, and the session returns to ready. Interrupting an idle session is a no-op.

Interrupt
curl -X POST https://api.t0.sikasio.com/v1/sessions/sess_7f3a9c2e/interrupt -H "Authorization: Bearer t0k_live_YOUR_KEY"

Messages & streaming

Sending a message is fire-and-forget: it returns 202 with a turn_id as soon as the prompt is written into the running agent. All output arrives on the event stream, so a slow turn never holds an HTTP request open.

Send a message

POST/v1/sessions/:id/messagesrequires API key
FieldTypeDefaultNotes
contentrequiredstring—The message for the agent.
streambooleantrueEmit text.delta events as the response is produced. With false you still get turn.completed, just no token-by-token output.
Send a message
curl -X POST https://api.t0.sikasio.com/v1/sessions/sess_7f3a9c2e/messages \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Add a failing test for the invoice date parser, then make it pass.",
    "stream": true
  }'
Response — 202
{ "turn_id": "turn_04c7", "session_id": "sess_7f3a9c2e", "state": "working" }

A session that is sleeping wakes on the message. A message sent while a turn is already running returns 409 session_not_ready — interrupt first, or wait for turn.completed.

Event stream (SSE)

GET/v1/sessions/:id/eventsrequires API key

Server-Sent Events, one ordered and sequence-numbered stream per session. The id of each frame is that sequence number.

On the wire
id: 126
event: turn.started
data: {"turn_id":"turn_04c7","seq":126}

id: 127
event: text.delta
data: {"turn_id":"turn_04c7","seq":127,"text":"Reading src/parser.ts"}

id: 128
event: tool.started
data: {"turn_id":"turn_04c7","seq":128,"tool":"shell","summary":"npm test"}

id: 129
event: tool.output
data: {"turn_id":"turn_04c7","seq":129,"tool":"shell","chunk":"2 passing\n"}

id: 130
event: file.changed
data: {"seq":130,"path":"src/parser.ts","change":"modified","bytes":4192}

id: 131
event: turn.completed
data: {"turn_id":"turn_04c7","seq":131,"usage":{"session_seconds":38,"tokens_in":12480,"tokens_out":842,"cache_read":11200,"cache_write":1280}}
EventCarries
session.readyThe sandbox is claimed and the agent process is up.
turn.startedturn_id — a message has been accepted and work has begun.
text.deltaA fragment of the agent’s response, unbuffered.
tool.startedThe tool the agent reached for, and a one-line summary.
tool.outputA chunk of that tool’s output — shell stdout, test results.
file.changedA workspace path that was created, modified or deleted, with its new size.
turn.completedThe turn is done, and carries the usage it cost: session seconds, tokens in and out, cache reads and writes.
session.idleThe session has slept. Metering stops here; the workspace is kept.
session.expiredThe workspace has been deleted. The stream ends.
errorSomething went wrong inside the session, in the same { code, message } shape as an HTTP error.

Resuming a dropped stream

Reconnect with a Last-Event-ID header carrying the last sequence number you processed and the stream continues from the next event — nothing lost, nothing replayed twice. This is the standard SSE reconnect header, so a browser EventSource does it for you; a hand-rolled client should track the id itself, as below. Events are retained for a replay window generous enough to cover a reconnect, not as a permanent log — for a long turn, keep your own copy.

Stream and resume
# -N disables curl's own buffering so deltas appear as they are produced
curl -N https://api.t0.sikasio.com/v1/sessions/sess_7f3a9c2e/events \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -H "Accept: text/event-stream"

# reconnecting after a drop: resume from the last id you saw, nothing is lost
curl -N https://api.t0.sikasio.com/v1/sessions/sess_7f3a9c2e/events \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -H "Accept: text/event-stream" \
  -H "Last-Event-ID: 128"

WebSocket

WS/v1/sessions/:id/socketrequires API key

The same events, plus the ability to push. Send { "type": "message", "content": "…" } to start a turn and { "type": "interrupt" } to stop one, on the same connection the events arrive on. Use it when you want one connection instead of two; use SSE when you want the simplest possible client and free reconnection.

Bidirectional socket
# curl 8 speaks WebSocket; --no-buffer keeps frames unbuffered
curl --include --no-buffer \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: c2FtcGxlLWtleS0xMjM0NTY=" \
  https://api.t0.sikasio.com/v1/sessions/sess_7f3a9c2e/socket

Files

Files move in and out at any time — before a message, during a turn, or long after the session has gone to sleep. Every path is resolved and confined to /workspace before it reaches the container; anything that escapes it is rejected with 400 invalid_path without ever being touched by the sandbox.

Two transfer paths, chosen by size. Files under 10 MB stream straight through the API into the live workspace — one hop, lowest latency. Larger or bulk transfers are handed a presigned object-storage URL so the bytes never pass through our servers, which is why the upload ceiling is generous.

Upload a file

PUT/v1/sessions/:id/files/*pathrequires API key

Raw body, no multipart wrapper. Creates parent directories as needed and overwrites silently. Returns 201 with the stored path and byte count.

Upload one file
curl -X PUT https://api.t0.sikasio.com/v1/sessions/sess_7f3a9c2e/files/src/parser.ts \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @src/parser.ts

Bulk upload

POST/v1/sessions/:id/filesrequires API key

A tar.gz body, unpacked into /workspace. Every entry in the archive is path-checked the same way a single upload is, and an archive containing one bad entry is rejected whole. This is the right way to seed a repository into a fresh session.

Upload a tree
# whole trees go up as one tar.gz — one request instead of a thousand
tar czf - src package.json | curl -X POST https://api.t0.sikasio.com/v1/sessions/sess_7f3a9c2e/files \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -H "Content-Type: application/gzip" \
  --data-binary @-

Download a file

GET/v1/sessions/:id/files/*pathrequires API key

The raw bytes, with the content type inferred from the extension. This works on a sleeping session too, served straight from the workspace snapshot — you never have to pay to wake a session just to fetch its results.

Download a file
# works while the session is asleep — served from the snapshot, no wake needed
curl -s https://api.t0.sikasio.com/v1/sessions/sess_7f3a9c2e/files/dist/report.pdf \
  -H "Authorization: Bearer t0k_live_YOUR_KEY" \
  -o report.pdf

List files

GET/v1/sessions/:id/files?path&recursiverequires API key
List the workspace
curl -s "https://api.t0.sikasio.com/v1/sessions/sess_7f3a9c2e/files?path=/&recursive=true" -H "Authorization: Bearer t0k_live_YOUR_KEY"
Response — 200
{
  "path": "/",
  "files": [
    { "path": "package.json",   "bytes": 412,   "modified_at": "2026-08-17T09:15:44.002Z" },
    { "path": "src/parser.ts",  "bytes": 4192,  "modified_at": "2026-08-17T09:18:10.551Z" },
    { "path": "dist/report.pdf","bytes": 208133,"modified_at": "2026-08-17T09:19:02.884Z" }
  ],
  "workspace_bytes": 41235968
}

Delete a file

DELETE/v1/sessions/:id/files/*pathrequires API key

Removes a file or, for a directory path, the tree beneath it. Returns 204.

Delete a file
curl -X DELETE https://api.t0.sikasio.com/v1/sessions/sess_7f3a9c2e/files/tmp/scratch.log -H "Authorization: Bearer t0k_live_YOUR_KEY"

Account & health

Who am I

GET/v1/merequires API key

The account behind the key, its plan and limits. The provider_key field (kept under its original name) reports { "configured": true, "managed": true }: model access is provided by the service, so there is never a token of yours behind it.

Read the account
curl -s https://api.t0.sikasio.com/v1/me -H "Authorization: Bearer t0k_live_YOUR_KEY"
Response — 200
{
  "account_id": "acct_51f0",
  "email": "you@example.com",
  "plan": "starter",
  "key": { "prefix": "t0_live_9f21", "name": "server", "last_used_at": "2026-08-17T09:14:02.117Z" },
  "limits": {
    "concurrent_sessions": 3,
    "credits_per_month": 3600,
    "credits_per_day": 600,
    "storage_bytes": 26843545600,
    "retention_days": 30
  },
  "provider_key": { "configured": true, "managed": true, "updated_at": null }
}

Usage

GET/v1/usage?from&torequires API key

Credits used and remaining, awake session seconds, turns, peak storage and token counts, with a daily breakdown. from and to are inclusive dates; the default window is the current billing month.

Read usage
curl -s "https://api.t0.sikasio.com/v1/usage?from=2026-08-01&to=2026-08-17" -H "Authorization: Bearer t0k_live_YOUR_KEY"
Response — 200
{
  "from": "2026-08-01",
  "to": "2026-08-17",
  "credits_included": 3600,
  "credits_used": 812,
  "credits_remaining": 2788,
  "session_seconds": 48702,
  "turns": 1394,
  "storage_bytes_peak": 5033164800,
  "tokens": { "in": 8114203, "out": 402118, "cache_read": 7411002, "cache_write": 703201 },
  "daily": [
    { "day": "2026-08-16", "session_seconds": 3120, "turns": 88, "credits": 52 },
    { "day": "2026-08-17", "session_seconds": 1980, "turns": 41, "credits": 33 }
  ]
}

Health

GET/healthpublic — no key

Liveness only. Cheap enough to poll from a load balancer.

Liveness
curl -s https://api.t0.sikasio.com/health

Status

GET/statuspublic — no key

Public component health — this is what the status page polls. It lives on its own host so it survives an outage of the stack it reports on.

Component health
curl -s https://api.t0.sikasio.com/status
Response — 200
{
  "components": [
    { "name": "gateway", "status": "operational", "p95_ms": 24 },
    { "name": "runner",  "status": "operational", "pool_depth": 12, "queue_depth": 0 },
    { "name": "storage", "status": "operational" }
  ],
  "error_rate_5m": 0.0009,
  "updated_at": "2026-08-17T09:20:00.000Z"
}

Parameters at a glance

Base URLhttps://api.t0.sikasio.com/v1
Auth headerAuthorization: Bearer t0k_live_YOUR_KEY
Modelslight · flash · core (default) · max
Session statesready · working · sleeping · expired · failed
Eventssession.ready · turn.started · text.delta · tool.started · tool.output · file.changed · turn.completed · session.idle · session.expired · error
idle_timeoutSeconds; default 600. Sleeping is never metered.
ttlSeconds; defaults to the plan retention window (Free 7 d · Starter 30 d · Pro 90 d).
Direct uploadUp to 10 MB per request; larger transfers get a presigned URL.
Workspace root/workspace — every path is confined to it.
Stream resumeLast-Event-ID: <seq> on the SSE endpoint.
CreditOne minute of awake session time.

Errors

One shape, everywhere — including the error event on a stream. Codes are stable and machine-readable; messages are for humans and should never be parsed. The request_id is what support needs to find your request in the logs.

{
  "error": {
    "code": "quota_exceeded",
    "message": "Daily credit cap reached for plan starter. Upgrade to pro for 2,400 credits per day.",
    "request_id": "req_0c41a7d9"
  }
}
StatusCodeMeaning
400invalid_pathA file path escaped /workspace, or was otherwise unusable. Rejected before it reached the container.
401invalid_keyMissing, malformed, unknown or revoked API key.
403forbiddenA valid key for the wrong account, or an action your plan does not allow.
404not_foundUnknown session, file or route — and what another account’s session looks like.
409session_not_readyThe session is busy with a turn, waking, or in a state that cannot accept the request yet.
412provider_key_requiredNot returned in managed mode: model access is included with your plan, so there is no token to be missing.
413payload_too_largeThe body is over the direct-upload ceiling, or the workspace would exceed your plan’s size limit.
429rate_limitedSomething is busy, not spent: the per-key request rate, or a concurrency ceiling (details.limit is concurrent_sessions or concurrent_runs). It clears on its own — retry after the Retry-After header.
429quota_exceededSomething is spent: monthly or daily credits, or storage. It clears when the window resets, when you buy credits, or on a bigger plan — never by retrying. The message names the limit and the next plan up, and details.resets_at says when it clears.
500internalOur fault. The message is deliberately generic; quote the request_id and we can find it.
503unavailableA dependency is briefly down. Nothing is broken on your side — retry with backoff.

Rate limits

Each key has a token bucket: short bursts are fine, sustained flooding is not. Exceeding it returns 429 rate_limited with a Retry-After header in seconds. Rate limits and quotas are both enforced at the front of the gateway, before any work is done — a rejected request never claims a container and never spends a credit.

Rate limiting is per key, so issuing a separate key per service keeps a runaway job from starving the rest of your account. Quotas, by contrast, are per account: concurrent sessions, credits, storage and retention are shared across every key you hold.

LimitScopeOn exceeding
Request ratePer key429 rate_limited + Retry-After
Concurrent sessionsPer account429 rate_limited + Retry-After
Concurrent runsPer account429 rate_limited after a short wait for a slot
Daily and monthly creditsPer account429 quota_exceeded; running turns finish
Workspace storagePer account413 payload_too_large on the write that would cross it

FAQ

What is T0?

T0 sells hosted, sandboxed AI coding-agent sessions over a REST API. You create a session, push files into it, send it messages, and stream the agent working in real time. Each session is an isolated Linux workspace that persists between messages, so files the agent wrote, packages it installed and git state are all still there on the next message.

T0 sells the runtime — isolation, persistence, orchestration and latency — with model access included in every plan. Base URL: https://api.t0.sikasio.com/v1.

How do I get an API key?

Create an account at https://t0.sikasio.com/signup with an email and a password, verify the code we email you, and your key is shown once — copy it straight away. Send it as an Authorization: Bearer header on every /v1 request.

Keys are the tenant boundary: they identify the account, authorize the request, and are what usage is metered against. Revoke one from the dashboard and it stops working within seconds.

Do I need my own coding-agent subscription?

No. Model access is included with your plan: sessions run on T0's own Claude Code account behind T0's gateway, so there is nothing to bring, no token to paste and no model credential to manage. The account credential never enters your sandbox, and your plan's credits are the only meter.

Which models can I use?

Two tiers, named by T0: core for everyday work and max for the hardest tasks. Set the tier when you create a session; core is the default. Model access is included with your plan, so the tier is the only model choice you make.

Can I just send a prompt and get an answer back?

Yes — POST /v1/runs takes one prompt and returns one answer in the same HTTP call. It creates a session, runs the turn, and hands the container back; you never see the session. Use it for classification, extraction, drafting and summarising, and use a session when the agent needs a workspace, tools or memory of what it just did.

Tools are off by default on a run, which is most of why it is cheap: an agent holding a toolkit spends billable seconds deciding whether to use it on a question that needed none. Send tools: "all" when the work genuinely needs the machine.

Ask for JSON with response_format — either json_object, or json_schema with a schema — and the answer comes back parsed in output_json. An answer that does not match is sent back once for repair before the call fails.

Runs are counted against their own concurrency ceiling rather than against concurrent sessions, so a burst of one-shot calls never locks you out of the sandboxes you are keeping open.

What persists between messages?

Everything inside /workspace, plus the agent process itself. The process stays alive for the life of the session, so conversation context stays resident and follow-up turns do not re-read everything from scratch. That is the single largest latency win in the system.

When a session goes idle the container stops but its volume survives, so resuming is a start rather than a rebuild. Workspaces are also snapshotted to object storage, which is what makes a resume possible days later.

What happens when a session goes idle?

After idle_timeout (10 minutes by default) the session moves to sleeping: the container stops, the workspace is kept, and a session.idle event is emitted. Sleeping costs nothing — metering counts awake seconds only.

The next message wakes it. A resume inside the warm window is a container start; a colder resume restores the workspace from object storage first. Files can be downloaded from a sleeping session without waking it.

How is usage metered?

A credit is one minute of awake session time. Sleeping sessions cost nothing. T0 meters awake session-seconds, peak workspace bytes and turns; token counts are recorded and shown to you for your own visibility — model usage is included with your plan and never billed separately.

Check live usage any time with GET https://api.t0.sikasio.com/v1/usage.

Is there a free plan?

Yes, permanently. Free is $0 for 60 credits a month (1 h of awake session time), 20 a day, 1 concurrent session, 400 MB of workspace storage and a 7-day retention window. No card required.

One rule to know: buying credits retires the free monthly allowance permanently — from your first purchase on, usage runs on the credits you bought. Credits never expire.

What are the plans and quotas?

Free — $0: 60 credits/month (1 h), 20/day, 1 concurrent session, 400 MB storage, 7-day retention, 1 GB / 1 CPU sandbox.

Starter — $19/month: 600 credits/month (10 h), 120/day, 3 concurrent sessions, 5 GB storage, 30-day retention, 2 GB / 1.5 CPU sandbox.

Pro — $59/month: 2,400 credits/month (40 h), 360/day, 10 concurrent sessions, 20 GB storage, 90-day retention, 4 GB / 2 CPU sandbox.

Is there a pay-as-you-go option?

Yes, and it works on every plan rather than instead of one. Buy credits and they sit unused until the monthly allowance runs out; from then on they pay for the minutes the allowance would have refused, at $0.04 / min. $10 buys 250 credits, $25 buys 625 credits.

That means running out mid-month is a choice rather than a wall: with no balance you get the usual 429 and wait for the reset, and with one the work carries on. Nothing changes plan, nothing is entered that has to be left, and the same rate applies whichever model tier you run.

Credits never expire, and they are spent by the second — a five-second turn costs five seconds of credit, not a rounded-up minute. When the balance reaches zero the API stops rather than billing on.

What happens when I hit a quota?

Never a surprise bill. Past the monthly allowance, an account with credits keeps working and spends them; an account without gets 429 quota_exceeded naming the limit, the next plan up, and the option of a top-up. Requests past the per-key rate limit return 429 rate_limited with a Retry-After header. Sessions already awake are not killed mid-turn.

How do I stream a response?

POST a message to https://api.t0.sikasio.com/v1/sessions/:id/messages — it returns 202 with a turn_id immediately — and read events from GET https://api.t0.sikasio.com/v1/sessions/:id/events, which is Server-Sent Events. Events are ordered and sequence-numbered per session, so if the connection drops you reconnect with a Last-Event-ID header carrying the last sequence you saw and the stream resumes from there with nothing lost or repeated.

If you would rather push and pull over one connection, https://api.t0.sikasio.com/v1/sessions/:id/socket is a WebSocket that carries the same events and accepts messages and interrupts.

How do I get files in and out?

Small files go straight to the API: PUT the raw body to /v1/sessions/:id/files/<path> and it is streamed into the live workspace. Bulk uploads go as a tar.gz to POST /v1/sessions/:id/files. Large transfers are handed a presigned object-storage URL so the bytes never pass through our servers.

Every path is resolved and confined to /workspace before it reaches the container; traversal is rejected with 400 invalid_path.

How isolated is a session?

Each session is its own container: read-only root filesystem, all Linux capabilities dropped, no-new-privileges, a seccomp profile, a pids limit, and hard memory and CPU ceilings set by your plan. It runs as a non-root user and never sees a Docker socket, a host mount or host networking.

Egress is default-deny through an allowlist proxy that refuses every private network range, so a session can reach the model provider and — depending on plan — package registries, and nothing else. The proxy fails closed: if it is down, the sandbox has no network at all.

Where do I see whether T0 is up?

The status page at https://status.t0.sikasio.com shows live component health — gateway, runner, warm-pool depth, queue depth and recent error rate — plus incident notes. It is served from its own host so it stays up when the stack it reports on does not.

What does a first request look like?

curl -X POST https://api.t0.sikasio.com/v1/sessions -H "Authorization: Bearer t0k_live_YOUR_KEY" -H "Content-Type: application/json" -d '{"name":"my-first-session"}' — that returns a session id and a stream URL, and the session is ready in under a second because it comes from a pool of pre-booted containers.

Get a key

The free plan is permanent: 60 credits a month — 1 h of awake session time — 400 MB of workspace storage and a 7-day retention window, with no card required. Buying credits on the free plan retires the free monthly allowance for good — from then on your usage runs on the credits you bought. Model access is included, so you are running in a minute.

Get your free API key →

Questions, or something in these docs that does not match what the API did? support@t0.sikasio.com. Health at https://api.t0.sikasio.com/health, live component status at status.t0.sikasio.com.