PROVISION X · AGENT API v1

Agents with a
computer built in.

Create an agent and it wakes up on its own machine, already running — full Linux desktop, Chrome, shell, persistent disk, everything preinstalled. Computer use isn't a tool it calls out to; it's where the agent lives. Send it work, stream the result, watch its screen. Powered by OpenClaw.


Start here

Quickstart

Five calls take you from nothing to a working agent you can watch. Base URL: {BASE_URL}/api/v1 (local dev: http://provision-x.test/api/v1).

1

Create an agent

A dedicated machine is forked and configured for it — allow about two minutes.

curl
curl -X POST $BASE/agents \
  -H "Authorization: Bearer pvx_live_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Riley","instructions":"You are a concise research assistant."}'
2

Wait for running

curl
curl $BASE/agents/agt_01m2... -H "Authorization: Bearer pvx_live_..."
# → { "status": "running", ... }   (webhook: agent.ready)
3

Send it work — one call

No session setup needed: omit session_id and one is minted for you.

curl
curl -X POST $BASE/agents/agt_01m2.../runs \
  -H "Authorization: Bearer pvx_live_..." \
  -H "Content-Type: application/json" \
  -d '{"input":"Research the top 3 EV makers, write a memo."}'
# → 202 { "id": "run_...", "session_id": "ses_...", "status": "queued" }
4

Stream the run

curl
curl -N $BASE/runs/run_.../stream -H "Authorization: Bearer pvx_live_..."
# event: run.created → run.status → run.completed { "output_text": "..." }
5

Continue the thread — or watch the screen

curl
curl -X POST $BASE/agents/agt_01m2.../runs \
  -H "Authorization: Bearer pvx_live_..." \
  -H "Content-Type: application/json" \
  -d '{"input":"Make it shorter.","session_id":"ses_..."}'

curl -X POST $BASE/agents/agt_01m2.../desktop_links \
  -H "Authorization: Bearer pvx_live_..."
# → { "url": "https://…" }  — a live stream of the agent's desktop

Start here

Authentication

Every request sends a bearer key. Keys are shown once at creation and are rate-limited to 120 requests per minute.

Authorization: Bearer pvx_live_...

Start here

Conventions & errors

  • Ids are prefixed ULIDsagt_ agent, ses_ session, run_ run, msg_ message, sub_ sub-agent.
  • Timestamps are ISO 8601 strings. Lists wrap results as {"data": [...]}, newest first.
  • Idempotency — mutating requests accept an Idempotency-Key header; retrying with the same key returns the original resource.

Errors always use one envelope:

json
{ "error": { "type": "validation_error", "message": "...", "details": { ... } } }
TypeStatusWhen
unauthorized401Missing or invalid API key
not_found404No such resource
validation_error422Bad input; per-field details included
rate_limited429Over 120 requests/minute
agent_unavailable409Agent is provisioning or errored
subagent_deleted, subagent_busy, agent_outdated, roster_busy, subagent_limit, slug_unavailable409Sub-agent lifecycle conflicts

Resource

Agents

An agent is an OpenClaw instance living on its own persistent, isolated machine — browser, desktop, shell, and files are its native abilities, ready the moment it's created. Create one per end user; it keeps files, memory, and connected accounts until you delete it.

provisioningrunningsuspendederrordeleted

suspended means the machine is stopped with its state kept on disk — it wakes automatically on the next run, adding ~30–60 s to that run.

POST /v1/agents

Returns 202 with the agent in provisioning; poll until running.

FieldTypeNotes
namestringRequired.
instructionsstringThe agent's persona and operating rules.
modelstringAlias: smart (default), fast, cheap.
external_idstringYour attribution tag (your user id), echoed back and filterable.
webhook_urlstringReceives agent.ready, run.completed, run.needs_input, run.failed.
auto_sleepbooleanDefault true — suspend the machine when idle.
idle_timeout_secondsinteger300–86400, default 1800.
metadataobjectYours; stored, never interpreted.
llm_keystringPer-agent LLM credential — write-only, stored encrypted, never returned. Platform default when omitted.
llm_base_urlstringPer-agent OpenAI-compatible endpoint. Platform default when omitted.

GET /v1/agents

Filters: ?external_id=, ?status=, ?limit= (default 25).

GET /v1/agents/{id}

Status reflects live machine state. Includes auto_sleep, idle_timeout_seconds, last_activity_at, ready_at, last_error.

DELETE /v1/agents/{id}

Destroys the machine and everything on it. Irreversible.

POST /v1/agents/{id}/desktop_links

Returns a live view of the agent's actual desktop. Wakes a suspended machine. url opens in a browser tab (on some machine platforms it requires operator access); websocket, when present, is the customer-facing path — a short-lived, single-use stream grant your application feeds to an embedded viewer.

Pass an optional body {"origin": "https://app.example.com"} — your web app's exact browser origin — to get an origin-bound grant the page can connect to directly (ws_url?token=, e.g. with noVNC). The origin must be registered with the machine platform first (mola: Settings → Desktop origins; HTTPS origins, plus http://localhost/http://127.0.0.1 for development). Without an origin, the grant only accepts no-Origin clients (native apps or server-side relays) — browsers are refused.

response
{
  "mode": "view",
  "url": "https://...",
  "expires_at": null,
  "websocket": { "ws_url": "wss://...", "token": "...", "expires_at": "..." }
}
Links expire. Request a fresh link each time the user opens the view; never cache the websocket token or embed it in shipped frontend code.

Resource

Runs

A run is one agentic turn: your input, the agent's work — it can browse, run code, use its desktop, and reason across many steps — and its reply.

queuedrunningcompletedneeds_inputfailedcancelled
needs_input. When outcome classification is enabled, a run where the agent stopped to ask you something (a clarifying question, missing credentials, an approval) finishes as needs_input instead of completed, with the question in input_request.message and the raw classification in result.outcome. Answer by sending the next input on the same session — the conversation picks up where it stopped.

POST /v1/agents/{id}/runs

The one-call turn API. Omit session_id to mint a new conversation; reuse the returned one to continue it. Wakes a suspended agent automatically.

FieldTypeNotes
inputstringRequired. The message or task.
session_idstringContinue a thread; omit to start one.
subagent_idstringOnly when minting — routes the new session to that sub-agent.
streambooleantrue responds with the SSE stream instead of JSON.
metadataobjectEchoed back on the run.
response · 202
{
  "id": "run_...", "object": "run", "session_id": "ses_...",
  "status": "queued", "output_text": null, ...
}

GET /v1/runs/{id}

Once terminal, output_text carries the agent's full reply, untruncated.

response
{
  "id": "run_...", "session_id": "ses_...", "status": "completed",
  "output_text": "Here is the memo...",
  "error": null, "started_at": "...", "ended_at": "..."
}

GET /v1/runs/{id}/stream

Server-Sent Events until the run is terminal. Attach any time — during the run or after. Use curl -N to watch live.

events
event: run.created
data: {"id":"run_...","session_id":"ses_...","status":"queued"}

event: run.status
data: {"id":"run_...","status":"running"}

event: run.completed
data: {"id":"run_...","status":"completed","output_text":"...","error":null}
Status-level streaming. Events mark run lifecycle, not individual tokens. Terminal events are run.completed, run.needs_input, run.failed, run.cancelled; after ~11 minutes without one the stream closes with run.timeout while the run itself keeps executing — re-attach or poll.

POST /v1/runs/{id}/cancel

Marks a non-terminal run cancelled.


Resource

Sessions & messages

A session is one conversation. History lives on the agent's machine, so you only ever send the new input. Sessions never expire and are never auto-reset — they live until deleted.

EndpointWhat it does
POST /v1/agents/{id}/sessionsCreate explicitly — {title?, external_id?, subagent_id?, metadata?}. (The one-call run API usually makes this unnecessary.)
GET /v1/agents/{id}/sessionsList an agent's sessions.
GET /v1/sessions/{id}Retrieve, with recent messages.
GET /v1/sessions/{id}/messagesFull history, oldest first — user and agent messages.
POST /v1/sessions/{id}/messagesSession-first send: {"content": "..."}{message, run}.
DELETE /v1/sessions/{id}Delete the conversation permanently.

Resource

Sub-agents

Extra personas on the same machine — each with its own workspace, instructions, and optional model, sharing the machine and its credentials. Creating or deleting one is hot: no restart, in-flight runs untouched. Up to 10 per agent.

A persona boundary, not a security boundary. Sub-agents share the machine. The default agent can also delegate to them on its own — chief-of-staff wiring is automatic.
EndpointWhat it does
POST /v1/agents/{id}/subagents{"name", "instructions"?, "model"?, "browser_profile"?} → 201, synchronous (~5 s).
GET /v1/agents/{id}/subagentsList the roster.
GET /v1/agents/{id}/subagents/{sid}Retrieve one.
DELETE /v1/agents/{id}/subagents/{sid}Remove — 409 subagent_busy while it has queued or running work.

Choosing a browser mode

browser_profile chooses how the sub-agent browses:

ValueBehaviorUse when
"own" (default)Dedicated browser profile — separate cookies, logins, and tabs.Personas must never clobber each other's browser work.
"shared"The machine's default profile, shared with the main agent and other shared sub-agents.Personas collaborate on the same accounts — same logins, cookies, and connected integrations.
curl
# Isolated researcher (default): own cookies and logins
curl -X POST $BASE/agents/agt_.../subagents \
  -H "Authorization: Bearer pvx_live_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Scout","instructions":"You are Scout, a web researcher."}'

# Collaborator: shares the machine's browser — same logins as the main agent
curl -X POST $BASE/agents/agt_.../subagents \
  -H "Authorization: Bearer pvx_live_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Editor","instructions":"You polish drafts.","browser_profile":"shared"}'
# → 201 { "id": "sub_...", "slug": "editor", "browser_profile": "shared", ... }
What's shared either way: secrets (.secrets.env) and the machine's filesystem are visible to all personas on the machine; each sub-agent's workspace is always its own. browser_profile only controls browser state.

Route a conversation to a sub-agent with subagent_id — on the one-call run when minting, or on session create. After a sub-agent is deleted its message history is kept, and new messages to its sessions return 409 subagent_deleted.


Platform

Memory

Shared notes delivered to every workspace on the agent's machine as TEAM.md — the main agent and all sub-agents see the same file, and personas are instructed to read it before starting work. Synced live without a restart; survives re-provisioning. Null or empty content removes the file everywhere.

PUT /v1/agents/{id}/memory

Body: { "content": "..." | null }. Returns { content, updated_at }.

GET /v1/agents/{id}/memory


Platform

Secrets

Write-only credentials delivered to the agent's workspace as .secrets.env, synced live without a restart. Values can never be read back through the API.

EndpointWhat it does
PUT /v1/agents/{id}/secrets{"secrets": {"NAME": "value"}} — upsert.
GET /v1/agents/{id}/secretsNames and set_at only.
DELETE /v1/agents/{id}/secrets/{name}Remove one.

Platform

Webhooks

Set webhook_url on the agent to receive agent.ready, run.completed, run.needs_input, and run.failed. Every delivery is signed:

X-Signature: t=<unix_ts>,v1=<hex hmac-sha256>

v1 is HMAC-SHA256 of "{t}.{raw_body}" with your signing secret. Reject deliveries older than 5 minutes or with a mismatched signature. Failed deliveries retry with backoff: 10 s, 60 s, 5 m, 30 m.


Platform

Models

model on the agent is an alias — smart (default), fast, cheap — mapped server-side to concrete models (currently gpt-4.1 / gpt-4.1-mini / gpt-4.1-nano), and sub-agents accept the same aliases to override the machine default. Routing and billing are platform-managed by default; to bring your own routing, set llm_key (and optionally llm_base_url, any OpenAI-compatible endpoint) at agent creation. The credential is stored encrypted, pasted into the machine's auth store at provision time, and never returned by the API. It is machine-global: the agent and all its sub-agents share it.


Platform

Building with a coding agent?

The entire API lives in one plain-text file. Point Claude Code, Codex, or any coding agent at it and it can scaffold a working client:

Read {BASE_URL}/llms-full.txt and build me a client for the Provision X Agent API.

Open llms-full.txt →