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).
Create an agent
A dedicated machine is forked and configured for it — allow about two minutes.
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."}'
Wait for running
curl $BASE/agents/agt_01m2... -H "Authorization: Bearer pvx_live_..." # → { "status": "running", ... } (webhook: agent.ready)
Send it work — one call
No session setup needed: omit session_id and one is minted for you.
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" }
Stream the run
curl -N $BASE/runs/run_.../stream -H "Authorization: Bearer pvx_live_..." # event: run.created → run.status → run.completed { "output_text": "..." }
Continue the thread — or watch the screen
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 ULIDs —
agt_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-Keyheader; retrying with the same key returns the original resource.
Errors always use one envelope:
{ "error": { "type": "validation_error", "message": "...", "details": { ... } } }| Type | Status | When |
|---|---|---|
unauthorized | 401 | Missing or invalid API key |
not_found | 404 | No such resource |
validation_error | 422 | Bad input; per-field details included |
rate_limited | 429 | Over 120 requests/minute |
agent_unavailable | 409 | Agent is provisioning or errored |
subagent_deleted, subagent_busy, agent_outdated, roster_busy, subagent_limit, slug_unavailable | 409 | Sub-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.
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.
| Field | Type | Notes |
|---|---|---|
name | string | Required. |
instructions | string | The agent's persona and operating rules. |
model | string | Alias: smart (default), fast, cheap. |
external_id | string | Your attribution tag (your user id), echoed back and filterable. |
webhook_url | string | Receives agent.ready, run.completed, run.needs_input, run.failed. |
auto_sleep | boolean | Default true — suspend the machine when idle. |
idle_timeout_seconds | integer | 300–86400, default 1800. |
metadata | object | Yours; stored, never interpreted. |
llm_key | string | Per-agent LLM credential — write-only, stored encrypted, never returned. Platform default when omitted. |
llm_base_url | string | Per-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.
{
"mode": "view",
"url": "https://...",
"expires_at": null,
"websocket": { "ws_url": "wss://...", "token": "...", "expires_at": "..." }
}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.
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.
| Field | Type | Notes |
|---|---|---|
input | string | Required. The message or task. |
session_id | string | Continue a thread; omit to start one. |
subagent_id | string | Only when minting — routes the new session to that sub-agent. |
stream | boolean | true responds with the SSE stream instead of JSON. |
metadata | object | Echoed back on the run. |
{
"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.
{
"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.
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}
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.
| Endpoint | What it does |
|---|---|
POST /v1/agents/{id}/sessions | Create explicitly — {title?, external_id?, subagent_id?, metadata?}. (The one-call run API usually makes this unnecessary.) |
GET /v1/agents/{id}/sessions | List an agent's sessions. |
GET /v1/sessions/{id} | Retrieve, with recent messages. |
GET /v1/sessions/{id}/messages | Full history, oldest first — user and agent messages. |
POST /v1/sessions/{id}/messages | Session-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.
| Endpoint | What it does |
|---|---|
POST /v1/agents/{id}/subagents | {"name", "instructions"?, "model"?, "browser_profile"?} → 201, synchronous (~5 s). |
GET /v1/agents/{id}/subagents | List 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:
| Value | Behavior | Use 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. |
# 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", ... }
.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.
| Endpoint | What it does |
|---|---|
PUT /v1/agents/{id}/secrets | {"secrets": {"NAME": "value"}} — upsert. |
GET /v1/agents/{id}/secrets | Names 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.