Get started free
Build on it

HTTP API

Everything your sandbox does, it does over one HTTP API on hardware you own. The browser is only its first client — an extension, a script or a CI job calls the same routes with the same credential.

There is no intentic API in the cloud that reaches your code. The platform stores your identity and your sandbox's address and nothing else; the API on this page is served by your daemon, inside your container, and the only way in is a credential you minted.

This page is long and you probably want one part of it:

SectionWhat it covers
Where it is Your sandbox's hostname, the loopback name, and the one open route.
How a call is authorized Sessions for a person, control tokens for a program, and what each scope reaches.
The shape of a call Where input rides, what comes back, and the one convention that covers every route.
Drive an agent Start a turn, follow it, and pick the conversation it lands in.
The route surface Every route group and what it's for — the map of the whole API.
Streams The server-sent event feeds, and the frames each one emits.
Routes that aren't JSON File bytes, archives and images, and how to ask for them.
Failures Every status this API returns and what it actually means.
Open on purpose The routes that take no credential, and why each one is safe.
Old daemon, new client What happens when the two ends are different versions.

Where it is

Each sandbox answers on its own hostname, https://sandbox-<id>.<zone>, over its private Cloudflare tunnel. The app shows the exact URL on Sandbox → Overview. One route is deliberately open — /health, the "is a daemon there" probe every launch script and readiness loop makes.

Any machine
curl https://sandbox-a1b2c3d4e5f6.intentic.dev/health

{ "ok": true, "sandboxId": "a1b2c3d4e5f6", "boot": { }, "announce": { } }

The sandbox id in that answer is how a caller proves it reached this daemon rather than something else on the same port. On the machine running the sandbox there is also https://local-<id>.<zone>, a public DNS name that resolves to 127.0.0.1 — a real certificate on a loopback address, so a browser on that machine skips the round trip to Cloudflare and back.

How a call is authorized

Two kinds of caller, two credentials, and the difference is whether a person is present.

A person, in a browser

The sandbox authenticates the end user directly against Google: the browser presents a Google ID token, and the daemon verifies its signature against Google's published keys. The platform never holds or signs this credential, so a platform compromise cannot command your sandbox. The first verified identity to arrive becomes the owner; further people are granted access by the owner, with a role that decides how far they reach.

POST /system/session exchanges that token for a daemon-minted session, which is what every steady-state call presents as authorization: Bearer …. Calling it again with a still-valid session renews it.

A program, anywhere

Anything outside a browser presents a control token in the x-intentic-control header instead. A control token is shown to you exactly once at mint — the daemon keeps only its hash — and it can be revoked individually at any time.

Minting one as the owner
curl -X POST "$SANDBOX/system/control/tokens" \
  -H "authorization: Bearer $OWNER_SESSION" \
  -H "content-type: application/json" \
  -d '{"label":"nightly CI","scope":"read"}'

{ "id": "6f1c…", "token": "ict_9wQ…" }

The app mints one for you on Sandbox → Computers → Editor bridge, at editor scope, together with the settings snippet an ACP editor needs. Any other scope is minted through the route above, which requires an owner session.

What a scope reaches

ScopeReaches
editor One conversation — run a turn, answer a card it parked on, read transcripts, search the tree. What an ACP editor bridge holds. It cannot see the fleet or land work.
read Observation only — the fleet, past sessions, workspace search, listening ports. The one rung that is genuinely narrow, and the reason the ladder has a bottom.
drive Everything read sees, plus making an agent work: start, answer, steer and stop a turn. Stops short of anything that moves code into the main tree.
land Everything drive does, plus merging a conversation's worktree into the main tree, and discarding one. Separate because the usual arrangement is a program that works and a person who decides.

Worth saying plainly: at drive and above, a stolen token is the agent's reach, because driving an agent means editing files and running commands in this sandbox. read is the one that is genuinely narrower, which is why it exists separately rather than as a politeness. A token presented on a route outside its scope gets a 403 that says so, rather than a confusing 401.

The shape of a call

The API is described by a typed contract that the daemon implements and the browser is built against, served over OpenAPI. That has one consequence worth internalising: the contract's path is the URL, there is no envelope, and the body you send and the body you get back are the declared shapes.

ConventionDetail
Input on GETThe query string.
Input on everything elseA JSON body — including DELETE, which sends its parameters in the body rather than the query.
Path parametersWritten {repo}, {id} in the contract, URL-encoded in the request. root is the workspace repo itself.
OutputJSON, except the byte routes and the streams below.
Two calls
SANDBOX=https://sandbox-a1b2c3d4e5f6.intentic.dev

# GET — input rides the query string
curl "$SANDBOX/workspace/file?path=README.md" \
  -H "x-intentic-control: $INTENTIC_TOKEN"

# Everything else — input rides the JSON body, including DELETE
curl -X POST "$SANDBOX/git/root/commit" \
  -H "x-intentic-control: $INTENTIC_TOKEN" \
  -H "content-type: application/json" \
  -d '{"message":"fix the flaky test"}'

Drive an agent

The reason most scripts are here. A turn executes as a detached daemon-side run: starting it acks with a run id, and any number of clients render it by attaching — so a reload, a second window, another device, or your script all watch the same work identically, and nothing is lost if the caller goes away.

Start a turn and watch it
# Start a turn. It answers as soon as the run exists — the work is detached.
curl -X POST "$SANDBOX/agent" \
  -H "x-intentic-control: $INTENTIC_TOKEN" \
  -H "content-type: application/json" \
  -d '{"prompt":"Update the changelog for the last five commits.",
       "conversationId":"nightly-changelog",
       "isolated":true}'

{ "run": "run_8c2f…" }

# Watch it: server-sent events, from the start or from a cursor you kept.
curl -N -X POST "$SANDBOX/agent/attach" \
  -H "x-intentic-control: $INTENTIC_TOKEN" \
  -H "content-type: application/json" \
  -d '{"conversationId":"nightly-changelog","after":0}'

conversationId is yours to choose and is what makes a run resumable and addressable later. isolated: true runs the turn in that conversation's own git worktree instead of the shared tree, which is what lets several agents work at once without colliding; the work reaches your main tree only when something calls land.

The route surface

Around 230 routes in the groups below. Rather than list them here, where they would date, ask the daemon: the hello frame on /events advertises every route name this particular daemon implements.

GroupWhat it covers
agent One conversation: run a turn, attach to it, reply, steer, stop, rewind.
agents The fleet — every registered conversation, its cumulative diff, and landing or discarding its work.
sessions Past conversations and their transcripts.
workspace The /work tree: read and write files, search, health, classification, cloning, dependency setup.
git Per-repo git for every repo under /work — status, log, stage, commit, branch, stash, rebase, push.
history Daemon-owned snapshots of the workspace, with diff and restore.
system Identity, the live event stream, terminals, browsers, subagents, presence, sessions.
capabilities The connected integrations, and the streamed apply that provisions one.
secrets Env-var secrets: set, remove, list keys, inventory across every store, owner-only reveal.
settings Per-sandbox agent settings, token-savings reporting, the built-in system prompts.
extensions Installed extensions, their approved manifests, and their settings.
automations Scheduled and event-driven wake-ups, plus the owner's approval queue.
workflows Multi-session designs — create, edit, delete, run.
loops Run a conversation again until its goal is met: start, stop, list.
chores What every repo currently measures, and what has been done about it.
prepush The pre-push check: start it, poll its state, cancel it.
ci Pipelines on the workspace repos' remotes — runs, rerun, cancel, and open an agent on a failure.
panels Per-repository dev servers, and the content facts extensions detect on.
ports Listening TCP ports, and forwarding one through the preview proxy.
public The workspace outbox — publish a file to a public address, or withdraw it.
memory The agent's persistent memory notes: read, write, delete.
drafts Agent-proposed posts awaiting approval.
activity The append-only audit feed of what the agent did.
usage The durable spend ledger, grouped by day, provider, account and model.
logs Daemon-owned debug logs — terminal captures, CLI run logs, daemon.log.
push Web-push notification subscriptions for this sandbox.
inventory The deploy config's managed hosts and services.
intentic Run the in-sandbox intentic CLI and stream its output.
vpn Dial, drop and inspect the links a vpn capability configured.
komodo A connected Komodo orchestrator: stacks, deployments, servers, builds, logs.
claude Claude subscription OAuth, and the Claude accounts this sandbox holds.
grok xAI subscription OAuth and its connected account.
translator The routed subscriptions — Codex, Gemini, Grok and Kimi under the Claude Code harness.
codex, gemini, kimi, endpoints A model-catalog route each, for the model picker.

Streams

Long-lived routes emit server-sent events: blank-line separated frames, each carrying one data: line of JSON.

GET /events
curl -N "$SANDBOX/events" -H "authorization: Bearer $SESSION"

data: {"kind":"hello","workspaceId":"","routes":["agent.run","git.status", …],"build":""}

data: {"kind":"heartbeat"}

data: {"kind":"workspaceChanged","paths":["src/app.ts","README.md"]}

data: {"kind":"refsChanged","repos":["root"]}
StreamCarries
GET /events The sandbox's liveness and everything that changed out of band: a hello identity frame, heartbeats, boot progress, batches of changed file paths, the repo set, which repos' git refs moved, the presence roster and the fleet.
POST /agent/attachOne turn's frames, replayed from your cursor and then live.
POST /capabilities, /intentic/apply/eventsProgress lines while a capability or a deployment applies.
GET /system/terminalA WebSocket carrying a tmux session's bytes. A WebSocket cannot set headers, so it authorizes a short-lived ticket from the query string instead.

Those change pushes are not a nicety. An intentic workspace is file-first — the agent edits files and moves git refs with its own tools, out of band from every HTTP route — so without them a client is only ever as fresh as the last thing someone clicked.

Routes that aren't JSON

RouteWhy it's different
GET /workspace/rawA file's bytes with a content type by extension — how images and PDFs are previewed, where the text route would corrupt them.
GET /workspace/mediaA streamed byte range for audio and video. A media element cannot send a header, so this takes a path-scoped ticket minted by POST /workspace/media-ticket.
POST /workspace/uploadRaw bytes to ?path=, in parts via ?offset= so a large file stays under the edge's body cap.
GET /extensions/{id}/bundleAn installed extension's ESM bundle, as bytes.

Failures

StatusMeans
400The input didn't match the declared shape, or a path escaped the workspace.
401No credential, or one that doesn't verify. Indistinguishable, on purpose.
403A real credential that may not go there — an identity that isn't a member, a member below the route's role floor (the answer names the tier), or a control token outside its scope.
404No such route on this daemon, or no such thing. Also the answer for the daemon's own credential files, which are not readable through the file API.
413Too large — the file routes are bounded so one open log can't stall the daemon.

Open on purpose

A handful of routes check no bearer, because the caller provably cannot present one: /health, the web-chat widget's routes, an automation's webhook fire, a CI provider's webhook, a workflow gate, and the enrollment routes a new computer redeems a one-time pairing token on. Each carries its own narrower check instead.

Old daemon, new client

Your browser is routinely newer than the daemon it talks to — a released app plane serves every sandbox, whatever image each user last pulled. That stays supported, and it is why hello advertises route names: a client compares that list against the contract it was built with, so a route the daemon predates surfaces as a named gap instead of a 404 nobody can attribute. Pin nothing; ask.

Next

  • Host API: the same daemon from inside the app, typed, for an extension's views.
  • Manifest reference: how an extension declares which of these routes it may call.

Type to search every page of the docs.