Agents

One call that drives a computer until the task is done, the threads that continue it, and runs in the background.

Base URL https://app.mandala.computer/api/v1. Every request carries an API key, and failures take the shape under Errors.

POST/computers/{id}/agent

Run an agent loopmember+

One call that drives this computer until the task is done: the loop screenshots, asks a model what to do, performs it, and repeats. Answered by the control plane rather than forwarded to a hypervisor, and it can run for minutes.

The computer must already be RUNNING. This endpoint will not start one for you: starting is billable, and it is not a decision to make on somebody’s behalf because they sent a prompt. A stopped or suspended computer is refused with status 409, and so is one another agent run is already driving — that one with reason: "contention", because it ends when the other run does, as does one whose disk is still being made.

WITH STREAMING, THE DEFAULT, THOSE REFUSALS ARRIVE ON A 200. Every refusal found after the request is accepted — an unknown computer (404), one not running or already driven (409), a credential refused part way through, the model API’s own errors, and on an OpenAI run a batch, bash command or screenshot that failed — arrives on the stream as event: error with {request_id, error, status, reason?, cost_usd?, top_up_url?, usage, steps}, where status is the status it would have had and steps is the LIST of AgentSteps taken before it failed — the list stream: false calls steps_taken, not the count AgentResult.steps carries on done (an unexpected failure of the stream itself sends {request_id, error, status: 500} alone). Only stream: false answers them as HTTP statuses. What is checked before the request is accepted — the key, the role, the rate budget, the body, X-Model-Key — is an HTTP status either way.

Send X-Model-Key to use your own Anthropic or OpenAI key. The key is never stored; the provider bills you directly and no Mandala AI credits are spent. Select OpenAI with a gpt-*, o-series or computer-use-preview model, or provider: openai. A key whose prefix belongs to the other provider — an sk-ant- key for OpenAI, or any other sk- key for Anthropic — is a 400. OpenAI uses the Responses API with native computer batches and a bash function on this desktop. It sends prompts, screenshots and command output to OpenAI, using stored responses to continue the run. Each OpenAI batch is admitted in full against max_steps and the rate budget before any action runs; a batch that exceeds the remaining allowance is not partially executed. A batch, bash command or screenshot that fails ends an OpenAI run as a failure rather than with a stop: it carries the status, message and any reason the platform answered that call with, and the steps already taken — for a batch, the actions the desktop acknowledged, and the failed one with its error when the desktop said which it was. Nothing is replayed, so inspect the desktop before continuing. A command that runs and exits non-zero is not a failure; its output goes back to the model. OpenAI requires your own key. Funded runs are metered by Mandala. Without that header, eligible accounts can use funded AI credits when enabled. Funded runs require a plan, a supported Stripe billing country, and at least $0.25 available for the first model call; otherwise they are refused before streaming (402 with top_up_url for insufficient credits; 400, 403 or 503 otherwise, as each of those statuses says). Funded runs use the platform system prompt and tools, default to claude-sonnet-5-5, and also allow claude-opus-5-5. Custom system prompts and other initially selected models require your own key. Anthropic default fallback routing is enabled and can serve a broader model; each billed attempt uses that model’s rate. Each model call reserves $0.25, then settles actual usage at 1.3× list price (deployment configurable). A completed call can overrun its reserve or max_cost_usd; its full cost is charged before another call is admitted. Usage beyond remaining credits creates debt that survives credit expiry; future grants and top-ups settle it first. If credits become unavailable mid-run it ends with credits_exhausted; the cost cap ends with max_cost. Solo/Studio/Swarm monthly grants are $5/$10/$50 and expire at billing-period end. $10/$25/$50 top-ups expire 12 months from purchase. Without a plan, existing credits freeze while their original expiry continues; a purchase paid after plan removal is retained for issuance on resubscription if unexpired. Funded prompts, screenshots and tool results are sent to Anthropic under Mandala’s account. See [pricing](https://mandala.computer/pricing), [terms](https://mandala.computer/terms) and [subprocessors](https://mandala.computer/subprocessors).

The response STREAMS as text/event-stream by default, because a run can go a long time between anything worth saying and a buffered answer would be a minute of silence followed by everything at once. Send stream: false for a single JSON body at the end instead. On the hosted API a request that is not streaming is cut off at the edge after about 120 seconds (a 524 with no body), and the run’s result — its usage and the steps it took — is lost with it. The run is stopped where it was once the dropped connection reaches the platform, so do not assume it finished. Keep streaming, the default, for anything that may take longer.

A RUN IS KEPT IN A THREAD. Unless you send store: false, a run that acted on the desktop, or finished with an answer, is kept as one exchange — the prompt, and the run’s closing text and how it ended (aborted for one stopped part way, which may have done more than it reported) — in a thread on this computer, and its thread_id comes back on done (or the stream: false body) and on a failure’s error. Send that thread_id with the next prompt to continue the conversation: the model is given the earlier turns (the newest 64,000 characters of them, with a note when some are left out) and told they are done, and acts only on the new prompt. A run refused before it acted keeps nothing and starts no new thread; a continuation still names the thread you sent, while it exists. An account keeps 1000 threads: a new one takes the place of the least recently used that this key could delete, and when there is none (a workspace key whose workspace holds none, say) the run is not kept and has no thread_id — delete threads you no longer need. Threads are listed and read under /agent/threads, and are deleted with their computer.

A CLAUDE RUN CAN ZOOM. Except on claude-opus-4-5 to 4-7 and claude-sonnet-4-6, which use the older computer tool, the loop serves the computer toolset’s zoom: a close-up cut from a fresh capture of the screen, of the rectangle the model names in the last full screenshot’s coordinates, so it shows the screen as it is now, which can have changed since that screenshot. A zoom is a step (action: "zoom") and counts toward max_steps. One that is refused — no full screenshot shown yet, a rectangle outside it, or a capture replaced before it could be cropped — is a step with error, and the loop continues. OpenAI runs have no zoom.

EVERY ACTION THE LOOP TAKES SPENDS YOUR RATE BUDGET, the same budget your own calls draw on. An Anthropic step costs what the calls it makes would cost you: an input and the screenshot that shows it, one call for bash, a cursor read or a bare screenshot, and two screenshots for a zoom. A key action with repeat: n is one input per press and then the screenshot, n + 1 calls, so a single step can cost up to 101 (repeat is at most 100). OpenAI reserves, before each batch runs, the price of one input call for every action in it plus one screenshot for the screenshot after it; its initial screenshot and each bash call cost one call each. A run that exhausts it stops where it is and ends with stop: "rate_limited" rather than failing: the steps already taken are reported, and the work already done to the desktop stands. Treat it the way you treat max_steps — as a run that did not finish — and wait rather than raising anything.

A RUN CAN BE REFUSED PART WAY THROUGH. The API key it is running on is re-checked before every model call and before every action or OpenAI batch, so a key deleted — or a membership removed — mid-run stops the run where it is. That arrives as an error event on the stream (or the single JSON body when stream is false) carrying reason: "revoked" beside error, plus the usage and the steps already taken, which are real and are billed. Most of what this endpoint refuses carries NO word: the model API’s own failures and a stopped or suspended computer arrive without one. A computer another run is already driving, like one whose disk is still being made, carries reason: "contention". The run’s opening computer lookup, and an OpenAI run’s batch, bash command or screenshot, forward whatever the platform classified its own refusal as, so any of the words above can reach you through this frame. Read it the way you read reason anywhere else here, and treat an absent word as no classification.

Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials. SDK setup first GETs the computer. Python start/stop/suspend/restart also refresh it with another GET; TypeScript refreshes only if the action returns an acknowledgement. Requires an already-running computer and member+ authority. Set X_MODEL_KEY to your Anthropic key for this request only. Inspect the returned stop/finish reason; HTTP success does not promise task completion. Optional model IDs are Anthropic IDs passed through unchanged.

curl

base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body -X POST "${base_url%/}"'/computers/vm-a1b2c3d4e5f6/agent' \
  -H "Authorization: Bearer $MANDALA_API_KEY" \
  -H "X-Model-Key: ${X_MODEL_KEY:?Set X_MODEL_KEY}" \
  -H "Content-Type: application/json" \
  --data-raw '{"prompt":"Open Firefox and search for the weather in Lisbon","stream":false}'

Python — Mandala SDK 0.16.0

# Python 3.10+: pip install mandala-computer==0.16.0
import json, os
from mandala_computer import Client

with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
    id = "vm-a1b2c3d4e5f6"
    body = json.loads("{\"prompt\":\"Open Firefox and search for the weather in Lisbon\",\"stream\":false}")
    c = client.computers.get(id)
    result = c.agent_once(body["prompt"], model_key=os.environ["X_MODEL_KEY"])
    print(result)

TypeScript — Mandala SDK 0.16.0

// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";

const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const modelKey = process.env.X_MODEL_KEY;
if (!modelKey) throw new Error("Set X_MODEL_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const id = "vm-a1b2c3d4e5f6";
const body = {"prompt":"Open Firefox and search for the weather in Lisbon","stream":false};
const c = await client.computers.get(id);
const result = await c.agentOnce({prompt: body.prompt, modelKey});
console.log(JSON.stringify(result, null, 2));

200 Illustrative result for stream:false.

HTTP 200
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

{
  "steps": 1,
  "stop": "end_turn",
  "text": "Firefox is open with the search results for weather in Lisbon.",
  "usage": {
    "input_tokens": 1240,
    "output_tokens": 95,
    "cache_read_tokens": 0,
    "cache_write_tokens": 0
  },
  "steps_taken": [
    {
      "n": 1,
      "tool": "bash",
      "detail": "firefox 'https://www.google.com/search?q=weather+in+Lisbon' >/dev/null 2>&1 & → exit 0"
    }
  ]
}

200 Illustrative native SSE frames for stream:true or omitted stream.

HTTP 200
Content-Type: text/event-stream
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

: run starting

event: step
data: {"n":1,"tool":"bash","detail":"firefox 'https://www.google.com/search?q=weather+in+Lisbon' >/dev/null 2>&1 & → exit 0"}

event: text
data: {"text":"Firefox is open with the search results for weather in Lisbon."}

event: done
data: {"steps":1,"stop":"end_turn","text":"Firefox is open with the search results for weather in Lisbon.","usage":{"input_tokens":1240,"output_tokens":95,"cache_read_tokens":0,"cache_write_tokens":0}}

401 Platform bearer credential missing; not an Anthropic key diagnosis.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer

{
  "error": "Authentication required",
  "reason": "missing",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential invalid; not an Anthropic key diagnosis.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "invalid API key",
  "reason": "invalid",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential revoked; not an Anthropic key diagnosis.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "API key revoked",
  "reason": "revoked",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

405 Unsupported method on this public path; HEAD returns these headers without a body.

HTTP 405
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
Allow: POST, OPTIONS

{
  "error": "method not allowed",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

200 Illustrative error frame after the HTTP stream has opened; work may be incomplete.

HTTP 200
Content-Type: text/event-stream
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

event: error
data: {"request_id":"3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17","error":"model API: Anthropic request failed or returned an invalid response.","status":502,"usage":{"input_tokens":96,"output_tokens":24,"cache_read_tokens":64,"cache_write_tokens":32},"steps":[{"n":1,"tool":"computer","action":"screenshot","detail":"took a screenshot"}]}

Path parameters

idrequiredstring

The computer id.

Headers

X-Model-Keystring

Your own key for the selected Anthropic or OpenAI provider. Omit for eligible Anthropic-funded runs. A blank header is refused, and so is a key whose prefix belongs to the other provider (sk-ant- for OpenAI, any other sk- key for Anthropic).

Request body

promptrequiredstring

What you want done, in plain language. Required.

systemstring

A system prompt for the model. Requires your own X-Model-Key.

max_cost_usdnumber

Funded run admission budget in USD. Actual cost can exceed this by the final model call; BYOK ignores it.

max_stepsinteger

How many actions the loop may take before it stops. Defaults to 20, and 100 is the most it accepts — a value past that is a 400 rather than a quietly shorter run. A STEP IS ONE ACTION ON THE DESKTOP, not one exchange with the model, and the two do not line up in either direction: one model reply may ask for several actions and spends a step on each, while a reply that asks for none — or a paused turn, resubmitted — costs model tokens and no step at all. Nor does every step take a screenshot; a bash call or a cursor read does not, and a zoom is a step that takes two. So this bounds the WORK, and bounds your Anthropic spend only loosely. For funded runs, max_cost_usd limits admission but the final call can overrun it. For BYOK, manage spend with your provider. A run that reaches max_steps ends with stop: "max_steps" and the work already done to the desktop stands. null is the same as leaving it out.

provider"anthropic" | "openai"

Optional provider. Inferred from claude-*, gpt-*, o-series and computer-use-preview model ids; defaults to Anthropic when neither is supplied. Required for an unknown model namespace. A recognized model/provider mismatch is refused. OpenAI requires X-Model-Key.

modelstring

A model id for the selected provider. Funded default: claude-sonnet-5-5; claude-opus-5-5 is also selectable. BYOK supports Anthropic and OpenAI computer-capable models. Defaults: claude-opus-5-5 for Anthropic, gpt-6.1-sol for OpenAI. Funded Anthropic fallback routing can use other models.

streamboolean

Send false for one JSON body at the end instead of an event stream. This endpoint streams by default, so omitting it gives you text/event-stream. Only the JSON booleans true and false change the default; any other value, the string "false" included, is read as if it were left out. On the hosted API a request that is not streaming is cut off at the edge after about 120 seconds (a 524 with no body), and the run’s result — its usage and the steps it took — is lost with it. The run is stopped where it was once the dropped connection reaches the platform, so do not assume it finished. Keep streaming, the default, for anything that may take longer.

thread_idstring

A thread to continue: thr_ and twenty-four hex characters, from an earlier run’s thread_id. Its earlier turns are given to the model as the conversation so far, and this run’s exchange is added to it. A thread of another computer is a 409, as is a full one (100 turns); one this key cannot see is a 404.

storeboolean

Whether to keep this run’s exchange in a thread. Default true: a run that does something is kept, in the thread you named or a new one, and its thread_id comes back on the result. false keeps nothing, and cannot be sent with thread_id.

messagesobject[]

The conversation so far, oldest first and without this run’s prompt, for a caller that keeps its own rather than a thread here. Given to the model as earlier turns, which it is told are done. Cannot be sent with thread_id. A new thread starts with these turns.

Response

An SSE event log of what the loop did, or one JSON body when stream is false.

The stream’s events are step (an AgentStep, one per action), text ({text}, what the model said), done (the fields below without steps_taken: the run finished or stopped) and error (see above: the run failed; its steps is an AgentStep list, not a count). With stream: false the answer is the fields below, steps_taken included, and a run that fails answers its status with an AgentFailure body — {error, reason?, cost_usd?, top_up_url?, usage, steps_taken}.

The stream opens at once with a : run starting comment line, before the first model call, and sends : keepalive comment lines while the run goes quiet. Lines beginning : carry nothing and can be ignored.

Response fields

cost_usdnumber

Cumulative AI credit charge in USD, present on funded runs only.

stepsalwaysinteger

How many actions the loop took — a count; the actions themselves are steps_taken.

stopalwaysstring

Why the loop ended: end_turn (the model finished), max_steps (it reached max_steps), refusal (the model declined), pause_turn (the model kept pausing its turn), rate_limited (your rate budget ran out mid-run), credits_exhausted (funded credits unavailable), max_cost (the next reservation would exceed max_cost_usd), or another stop reason the model itself gave, such as max_tokens. OpenAI also reports incomplete for an unfinished response and safety_check when action approval is needed. Only end_turn means the task was finished; the others are a run that stopped, and the work it did to the desktop stands.

textalwaysstring

The model’s closing text — its answer, or why it could not get there.

usagealwaysAgentUsage
thread_idstring

The thread this exchange was kept in; send it as thread_id to continue. Absent with store: false.

steps_takenAgentStep[]

Every action, in order. On the stream: false body only; the stream’s done event carries the other fields, and each action arrived before it as a step event.

Failures

400

Also a funded run (no X-Model-Key) that this account cannot have: funded AI is off or not available to the account, the body sends system, names a model other than claude-sonnet-5-5 or claude-opus-5-5 (an OpenAI model included), or sets max_cost_usd outside 0.25 to 20. Send your own key as X-Model-Key instead. Answered before streaming. On an OpenAI run, also a 400 the platform answered a batch, bash command or screenshot the model asked for, relayed as the run’s failure.

401

On this route a 401 can also be the model provider’s. With X-Model-Key, your key was refused; on a funded run (no X-Model-Key) it is the platform’s own provider credential, the message is Mandala AI is unavailable, and there is nothing on your side to fix. Only its status is kept: the body is this route’s own error shape, with a sentence of ours rather than the provider’s, and none of the provider’s headers come with it.

402

Insufficient funded AI credits before streaming: an AgentCreditRefusal, {error: "Insufficient AI credits", top_up_url: "/billing"}, where top_up_url is a path on this origin rather than a full URL. If credits can no longer be held when the run makes its first model call, the same refusal and top_up_url arrive as the run’s failure instead, an AgentFailure. For BYOK, the model API’s own billing refusal is relayed on the run as an AgentFailure. On an OpenAI run, so is a 402 the platform answered a batch, bash command or screenshot with: your plan refusing to resume a computer that was suspended during the run, whose message names the limit.

403

Also a funded run for an account with no billing customer or an unsupported billing country (A supported billing country is required; use your own key.). Answered before streaming.

429

On this route a 429 is either your rate budget here (an Error body) or the model provider’s rate limit on your X-Model-Key — Anthropic’s rate_limit_error or OpenAI’s 429 — relayed with its status (an AgentFailure body). A funded run relays a 429 from Anthropic on Mandala’s account the same way, with the message Mandala AI is unavailable; a spend limit on that account is a 503 instead. The relayed one carries the model API’s own Retry-After, in whole seconds, when that API sent one, and may carry none: no wait is invented for it. Streaming, the run’s 429 arrives as a stream event and carries no header.

502

The model API could not be reached, or the call to it was cut off before it answered. An OpenAI call also ends the run with 502 when OpenAI answers with a 5xx, sends a response that cannot be read or that this platform cannot act on, or takes longer than 120 seconds. On an OpenAI run it can also come from the desktop: a 502 the platform answered a batch, bash command or screenshot with — the guest agent not answering, for one — is relayed, and the run answers 502 itself when the desktop returns a screenshot it cannot read or incomplete batch progress, or a call to it fails without an answer. Input is not retried: read steps_taken and look at the desktop before continuing.

503

Also funded AI being unavailable. Before streaming: Mandala AI is unavailable, or Billing country could not be verified; use your own key or try again. During a funded run: Mandala’s spend limit with the model provider was reached, or a model call could not be priced or recorded. The run ends with Mandala AI is unavailable, and the calls already made stay charged.

504

On this route, Anthropic’s own timeout_error from the model API, relayed with its status: the model call took too long, and the run stopped there. An OpenAI call that times out is a 502 instead.

529

The model API is overloaded (overloaded_error), relayed with its status intact. Not a status this platform sends of its own; the run stopped there, and a retry later can succeed. With your own Anthropic key, every status the model API answers reaches you as itself, with a fixed message naming the provider and status, in the AgentFailure body. OpenAI’s 4xx statuses reach you as themselves with a fixed message naming the status, and its 5xx as 502. A funded run relays Anthropic’s status with the message Mandala AI is unavailable, except that a spend limit on Mandala’s account is a 503.

POST/chat/completions

Drive a computer through an OpenAI-shaped endpointmember+

The agent loop behind a door an OpenAI client already knows how to open, for callers who would rather point an existing client at a base URL than integrate a new one. Same loop and the same X-Model-Key behavior as the BYOK computer agent. This route always requires your own key; funded AI is unavailable here.

Four things are ours rather than OpenAI’s. computer_id on the body is required, and there is nowhere else to put it: this endpoint has no computer in its path. provider and max_steps are optional and mean what they mean on /computers/{id}/agent. And the response carries an agent block beside the OpenAI fields, saying what the loop did. Everything else reads the way an OpenAI client already writes it — the prompt is the last user message, the user and assistant messages before it are the conversation so far (given to the model as turns it is told are done), and any system messages become the system prompt. thread_id and store keep and continue a conversation as on /computers/{id}/agent; with thread_id the thread’s own turns are the conversation and earlier messages you send are not read, so resending the whole history each call is harmless. A new thread starts with the messages you sent, and its id comes back as agent.thread_id (on the last chunk when streaming). Only the documented top-level fields are accepted; unknown fields are a 400.

BESIDES THE KEY IT NEEDS, ONE BEHAVIOUR DIFFERS FROM THE ROUTE ABOVE, because this door follows OpenAI’s contract rather than ours: this endpoint does not stream unless you ask it to — omit stream and you get one JSON completion, which is the opposite of /computers/{id}/agent. model and provider choose the provider the same way on both routes; use a model that supports computer use, such as gpt-6.1-sol.

On the hosted API a run that does not stream — this endpoint’s default — is cut off at the edge after about 120 seconds (a 524 with no body), and its result, the usage and the steps it took, is lost with it. The run is stopped where it was once the dropped connection reaches the platform, so do not assume it finished. A run at the default max_steps can easily take longer: send stream: true for anything that may.

The computer must already be running — this endpoint will not start one for you, because starting is billable and not a decision to make on your behalf.

Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials. Requires an already-running computer and member+ authority. Set X_MODEL_KEY to your Anthropic key for this request only. Inspect the returned stop/finish reason; HTTP success does not promise task completion. Optional model IDs are Anthropic IDs passed through unchanged.

curl

base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body -X POST "${base_url%/}"'/chat/completions' \
  -H "Authorization: Bearer $MANDALA_API_KEY" \
  -H "X-Model-Key: ${X_MODEL_KEY:?Set X_MODEL_KEY}" \
  -H "Content-Type: application/json" \
  --data-raw '{"computer_id":"vm-a1b2c3d4e5f6","messages":[{"role":"user","content":"Open Firefox and search for the weather in Lisbon"}]}'

Python — direct HTTP (no released wrapper)

# Direct HTTP: this operation has no wrapper in mandala-computer==0.16.0.
# Python 3.10+; standard library only.
import json, os
from urllib.request import Request, urlopen

base_url = (os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1").rstrip("/")
url = base_url + "/" + "chat" + "/" + "completions"
model_key = os.environ.get("X_MODEL_KEY")
if not model_key:
    raise ValueError("Set X_MODEL_KEY")
headers = {"Authorization": "Bearer " + os.environ["MANDALA_API_KEY"], "X-Model-Key": model_key, "Content-Type": "application/json"}
body = json.loads("{\"computer_id\":\"vm-a1b2c3d4e5f6\",\"messages\":[{\"role\":\"user\",\"content\":\"Open Firefox and search for the weather in Lisbon\"}]}")
request = Request(url, headers=headers, method="POST", data=json.dumps(body).encode("utf-8"))
with urlopen(request) as response:
    print(json.load(response))

TypeScript — direct HTTP (no released wrapper)

// Direct HTTP: this operation has no wrapper in mandala-computer@0.16.0.
// Node.js 22+; no package required.

const baseUrl = (process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1").replace(/\/+$/, "");
const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const url = new URL(baseUrl + "/" + "chat" + "/" + "completions");
const modelKey = process.env.X_MODEL_KEY;
if (!modelKey) throw new Error("Set X_MODEL_KEY");
const headers: Record<string, string> = {Authorization: "Bearer " + apiKey, "X-Model-Key": modelKey, "Content-Type": "application/json"};
const body = {"computer_id":"vm-a1b2c3d4e5f6","messages":[{"role":"user","content":"Open Firefox and search for the weather in Lisbon"}]};
const response = await fetch(url, {method: "POST", headers, body: JSON.stringify(body)});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
console.log(await response.json());

200 Illustrative successful response.

HTTP 200
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

{
  "id": "agentrun-vm-a1b2c3d4e5f6-a1b2c3d4e5f6",
  "object": "chat.completion",
  "created": 1789560000,
  "model": "computer-use-agent",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Firefox is open with the search results for weather in Lisbon."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 1240,
    "completion_tokens": 95,
    "total_tokens": 1335
  },
  "agent": {
    "computer_id": "vm-a1b2c3d4e5f6",
    "steps": 1,
    "stop": "end_turn"
  }
}

200 Illustrative OpenAI-shaped SSE chunks for stream:true.

HTTP 200
Content-Type: text/event-stream
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

: run starting

data: {"id":"agentrun-vm-a1b2c3d4e5f6-a1b2c3d4e5f6","object":"chat.completion.chunk","created":1789560000,"model":"computer-use-agent","choices":[{"index":0,"delta":{"role":"assistant","content":"[1] firefox 'https://www.google.com/search?q=weather+in+Lisbon' >/dev/null 2>&1 & → exit 0\n"},"finish_reason":null}]}

data: {"id":"agentrun-vm-a1b2c3d4e5f6-a1b2c3d4e5f6","object":"chat.completion.chunk","created":1789560000,"model":"computer-use-agent","choices":[{"index":0,"delta":{"content":"Firefox is open with the search results for weather in Lisbon."},"finish_reason":null}]}

data: {"id":"agentrun-vm-a1b2c3d4e5f6-a1b2c3d4e5f6","object":"chat.completion.chunk","created":1789560000,"model":"computer-use-agent","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

401 Platform bearer credential missing; not an Anthropic key diagnosis.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer

{
  "error": "Authentication required",
  "reason": "missing",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential invalid; not an Anthropic key diagnosis.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "invalid API key",
  "reason": "invalid",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential revoked; not an Anthropic key diagnosis.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "API key revoked",
  "reason": "revoked",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

405 Unsupported method on this public path; HEAD returns these headers without a body.

HTTP 405
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
Allow: POST, OPTIONS

{
  "error": "method not allowed",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

502 A run failure uses the nested OpenAI envelope. Streaming failures can arrive after HTTP 200.

HTTP 502
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

{
  "error": {
    "message": "model API: Anthropic request failed or returned an invalid response.",
    "code": 502,
    "usage": {
      "prompt_tokens": 96,
      "completion_tokens": 24,
      "total_tokens": 120
    },
    "steps": [
      {
        "n": 1,
        "tool": "computer",
        "action": "screenshot",
        "detail": "took a screenshot"
      }
    ],
    "agent": {
      "computer_id": "vm-a1b2c3d4e5f6",
      "usage": {
        "input_tokens": 96,
        "output_tokens": 24,
        "cache_read_tokens": 64,
        "cache_write_tokens": 32
      },
      "steps": [
        {
          "n": 1,
          "tool": "computer",
          "action": "screenshot",
          "detail": "took a screenshot"
        }
      ]
    }
  },
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

200 Illustrative error frame after the HTTP stream has opened; work may be incomplete.

HTTP 200
Content-Type: text/event-stream
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

data: {"error":{"message":"model API: Anthropic request failed or returned an invalid response.","code":502,"usage":{"prompt_tokens":96,"completion_tokens":24,"total_tokens":120},"steps":[{"n":1,"tool":"computer","action":"screenshot","detail":"took a screenshot"}],"agent":{"computer_id":"vm-a1b2c3d4e5f6","usage":{"input_tokens":96,"output_tokens":24,"cache_read_tokens":64,"cache_write_tokens":32},"steps":[{"n":1,"tool":"computer","action":"screenshot","detail":"took a screenshot"}]}},"request_id":"3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"}

data: [DONE]

Headers

X-Model-Keyrequiredstring

Your own key for the selected Anthropic or OpenAI provider. Never stored. The provider bills you directly; no Mandala AI credits are spent. A key whose prefix belongs to the other provider (sk-ant- for OpenAI, any other sk- key for Anthropic) is a 400.

Request body

computer_idrequiredstring

Which of your computers to drive. Required.

messagesrequiredobject[]

OpenAI-shaped messages. The last user message is the prompt. Required.

provider"anthropic" | "openai"

Optional provider. Inferred from claude-*, gpt-*, o-series and computer-use-preview model ids; defaults to Anthropic when neither is supplied. Required for an unknown model namespace. A recognized model/provider mismatch is refused. OpenAI requires X-Model-Key.

modelstring

A computer-capable Anthropic or OpenAI model id, passed to the selected provider unchanged. Defaults: claude-opus-5-5 for Anthropic, gpt-6.1-sol for OpenAI.

max_stepsinteger

How many actions the loop may take before it stops, as on POST /computers/{id}/agent — defaults to 20, and 100 is the most it accepts. A value past that is a 400. Not an OpenAI field; it is ours, and an OpenAI client that does not send it gets the default, as does null.

streamboolean

Send true for an event stream. This endpoint does not stream by default, so omitting it gives you one JSON body — the opposite of POST /computers/{id}/agent. Only the JSON booleans true and false change the default; any other value, the string "false" included, is read as if it were left out. On the hosted API a run that does not stream — this endpoint’s default — is cut off at the edge after about 120 seconds (a 524 with no body), and its result, the usage and the steps it took, is lost with it. The run is stopped where it was once the dropped connection reaches the platform, so do not assume it finished. A run at the default max_steps can easily take longer: send stream: true for anything that may.

thread_idstring

A thread to continue: thr_ and twenty-four hex characters, from an earlier run’s thread_id. Its earlier turns are given to the model as the conversation so far, and this run’s exchange is added to it. A thread of another computer is a 409, as is a full one (100 turns); one this key cannot see is a 404.

storeboolean

Whether to keep this run’s exchange in a thread. Default true: a run that does something is kept, in the thread you named or a new one, and its thread_id comes back on the result. false keeps nothing, and cannot be sent with thread_id.

Response

One ChatCompletion, or with stream: true an SSE stream of OpenAI-style chunks.

Each chunk is data: {id, object: "chat.completion.chunk", created, model: "computer-use-agent", choices: [{index: 0, delta, finish_reason}]}. The first chunk’s delta carries role: "assistant"; each action the loop takes arrives as a line of delta.content ([n] what it did), then the model’s text; the last chunk has an empty delta and the finish_reason described on ChatCompletionChoice. The stream carries no usage and no agent block, so a streamed run cannot tell a finished run from one that ran out of rate budget — send stream: false when that matters and the run is short enough. A run that fails arrives as data: {error, request_id} with the OpenAIErrorDetail shape under error. The stream ends with data: [DONE], and lines starting : carry nothing: a : run starting line first, then : keepalive lines while the run goes quiet.

Response fields

idalwaysstring

This run’s completion id, the same one every chunk of a streamed run carries.

objectalwaysstring
createdalwaysinteger

Unix seconds.

modelalwaysstring

Always computer-use-agent: a fixed label for this loop, NOT the model that ran it — that is the model you sent, or our default.

choicesalwaysChatCompletionChoice[]

Exactly one.

usagealwaysobject
agentalwaysobject

Failures

401

On this route a 401 can also be the model provider’s. With X-Model-Key, your key was refused; on a funded run (no X-Model-Key) it is the platform’s own provider credential, the message is Mandala AI is unavailable, and there is nothing on your side to fix. Only its status is kept: the body is this route’s own error shape, with a sentence of ours rather than the provider’s, and none of the provider’s headers come with it.

402

On this route, the model API’s own refusal relayed as it came (billing_error: the account behind your X-Model-Key has a billing problem). On an OpenAI run it can also be your plan here refusing to resume a computer that was suspended during the run, relayed the same way; its message names the limit.

429

On this route a 429 is either your rate budget here (an Error body) or the model provider’s rate limit on your X-Model-Key — Anthropic’s rate_limit_error or OpenAI’s 429 — relayed with its status (an OpenAIError body). The relayed one carries the model API’s own Retry-After, in whole seconds, when that API sent one, and may carry none: no wait is invented for it. Streaming, the run’s 429 arrives as a stream event and carries no header.

502

The model API could not be reached, or the call to it was cut off before it answered. An OpenAI call also ends the run with 502 when OpenAI answers with a 5xx, sends a response that cannot be read or that this platform cannot act on, or takes longer than 120 seconds. On an OpenAI run it can also come from the desktop: a 502 the platform answered a batch, bash command or screenshot with — the guest agent not answering, for one — is relayed, and the run answers 502 itself when the desktop returns a screenshot it cannot read or incomplete batch progress, or a call to it fails without an answer. Input is not retried: read steps and look at the desktop before continuing.

504

On this route, Anthropic’s own timeout_error from the model API, relayed with its status: the model call took too long, and the run stopped there. An OpenAI call that times out is a 502 instead.

529

The model API is overloaded (overloaded_error), relayed with its status intact. Not a status this platform sends of its own; the run stopped there, and a retry later can succeed. With an Anthropic key, every status the model API answers reaches you as itself, with a fixed message naming the provider and status, in the OpenAIError body. OpenAI’s 4xx statuses reach you as themselves with a fixed message naming the status, and its 5xx as 502.

GET/agent/threads

List agent threadsmember+

The conversations agent runs have kept, most recently used first: from POST /computers/{id}/agent and POST /chat/completions, unless a run sent store: false. A key confined to a workspace lists that workspace’s. An account keeps its 1000 most recently used threads; making another deletes the one used longest ago. Members and owners may read them; viewers cannot, since a prompt is where private text ends up.

Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials.

curl

base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body "${base_url%/}"'/agent/threads' \
  -H "Authorization: Bearer $MANDALA_API_KEY"

Python — Mandala SDK 0.16.0

# Python 3.10+: pip install mandala-computer==0.16.0
import os
from mandala_computer import Client

with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
    result = client.agent_threads.list()
    print(result)

TypeScript — Mandala SDK 0.16.0

// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";

const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const result = await client.agentThreads.list();
console.log(JSON.stringify(result, null, 2));

200 Illustrative successful response.

HTTP 200
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

{
  "threads": [
    {
      "id": "thr_0123456789abcdef01234567",
      "computer_id": "vm-a1b2c3d4e5f6",
      "title": "Open Firefox and search for the weather in Lisbon",
      "turns": 4,
      "created_at": "2026-10-18T09:00:00.000Z",
      "updated_at": "2026-10-18T09:04:00.000Z"
    }
  ],
  "has_more": false
}

401 Platform bearer credential missing.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer

{
  "error": "Authentication required",
  "reason": "missing",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential invalid.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "invalid API key",
  "reason": "invalid",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential revoked.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "API key revoked",
  "reason": "revoked",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

405 Unsupported method on this public path; HEAD returns these headers without a body.

HTTP 405
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
Allow: GET, HEAD, OPTIONS

{
  "error": "method not allowed",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

Query parameters

computer_idstring

Only this computer’s threads.

limitinteger

How many to return. Outside 1 to 100 is a 400.

beforestring

The id of the last thread of the previous page, for the page after it. One this key cannot see is a 400.

Response

One page of threads.

Response fields

threadsalwaysAgentThreadSummary[]
has_morealwaysboolean

Whether another page follows; pass the last thread’s id as before to get it.

GET/agent/threads/{thread}

Read an agent threadmember+

One thread and every turn in it, oldest first: each user turn is a prompt, and each assistant turn is how the run answered it — its closing text, stop, steps, and cost_usd on a funded run. A thread this key cannot see is a 404.

Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials.

curl

base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body "${base_url%/}"'/agent/threads/thr_0123456789abcdef01234567' \
  -H "Authorization: Bearer $MANDALA_API_KEY"

Python — Mandala SDK 0.16.0

# Python 3.10+: pip install mandala-computer==0.16.0
import os
from mandala_computer import Client

with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
    thread = "thr_0123456789abcdef01234567"
    result = client.agent_threads.get(thread)
    print(result)

TypeScript — Mandala SDK 0.16.0

// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";

const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const thread = "thr_0123456789abcdef01234567";
const result = await client.agentThreads.get(thread);
console.log(JSON.stringify(result, null, 2));

200 Illustrative successful response.

HTTP 200
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

{
  "id": "thr_0123456789abcdef01234567",
  "computer_id": "vm-a1b2c3d4e5f6",
  "title": "Open Firefox and search for the weather in Lisbon",
  "turns": 4,
  "created_at": "2026-10-18T09:00:00.000Z",
  "updated_at": "2026-10-18T09:04:00.000Z",
  "messages": [
    {
      "role": "user",
      "text": "Open Firefox and search for the weather in Lisbon",
      "created_at": "2026-10-18T09:00:00.000Z"
    },
    {
      "role": "assistant",
      "text": "Lisbon is 21 °C and sunny.",
      "stop": "end_turn",
      "steps": 6,
      "created_at": "2026-10-18T09:00:00.000Z"
    },
    {
      "role": "user",
      "text": "Now do the same for Porto",
      "created_at": "2026-10-18T09:04:00.000Z"
    },
    {
      "role": "assistant",
      "text": "Porto is 18 °C with light rain.",
      "stop": "end_turn",
      "steps": 4,
      "created_at": "2026-10-18T09:04:00.000Z"
    }
  ]
}

401 Platform bearer credential missing.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer

{
  "error": "Authentication required",
  "reason": "missing",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential invalid.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "invalid API key",
  "reason": "invalid",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential revoked.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "API key revoked",
  "reason": "revoked",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

405 Unsupported method on this public path; HEAD returns these headers without a body.

HTTP 405
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
Allow: GET, HEAD, DELETE, OPTIONS

{
  "error": "method not allowed",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

Path parameters

threadrequiredstring

A thread_id from an agent run or GET /agent/threads (thr_ followed by 24 lowercase hex characters).

Response

The thread.

Response fields

idalwaysstring

thr_ and twenty-four hex characters.

computer_idalwaysstring
titlealwaysstring
turnsalwaysinteger
created_atalwaysstring
updated_atalwaysstring
messagesalwaysAgentThreadTurn[]

Every turn, oldest first.

DELETE/agent/threads/{thread}

Delete an agent threadmember+

The thread and its turns are gone, and its id can no longer be continued. What the runs did to the desktop stands. A thread this key cannot see is a 404.

Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials.

curl

base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body -X DELETE "${base_url%/}"'/agent/threads/thr_0123456789abcdef01234567' \
  -H "Authorization: Bearer $MANDALA_API_KEY"

Python — Mandala SDK 0.16.0

# Python 3.10+: pip install mandala-computer==0.16.0
import os
from mandala_computer import Client

with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
    thread = "thr_0123456789abcdef01234567"
    result = client.agent_threads.delete(thread)
    print(result)

TypeScript — Mandala SDK 0.16.0

// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";

const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const thread = "thr_0123456789abcdef01234567";
const result = await client.agentThreads.delete(thread);
console.log(JSON.stringify(result, null, 2));

200 Illustrative successful response.

HTTP 200
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

{
  "ok": true
}

401 Platform bearer credential missing.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer

{
  "error": "Authentication required",
  "reason": "missing",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential invalid.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "invalid API key",
  "reason": "invalid",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential revoked.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "API key revoked",
  "reason": "revoked",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

405 Unsupported method on this public path; HEAD returns these headers without a body.

HTTP 405
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
Allow: GET, HEAD, DELETE, OPTIONS

{
  "error": "method not allowed",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

Path parameters

threadrequiredstring

A thread_id from an agent run or GET /agent/threads (thr_ followed by 24 lowercase hex characters).

Response

Gone.

Response fields

okboolean

Always true. A failure is a status code, not ok: false.

POST/computers/{id}/agent/runs

Start an agent run in the backgroundmember+

Give the computer a job and walk away: the same loop as POST /computers/{id}/agent, with the same body except stream (refused) and store: false (refused: a background run is kept so it can be read back), started and then read back by id instead of streamed. The run goes on when this request ends, and is not cut at the edge after 120 seconds the way a non-streaming agent call is.

This answers 202 once the run holds the computer, with the run as it stands (status: "running"). What refuses a run before that — an unknown or just-deleted computer (404), one not running (409), one already driven or whose workspace is changing (409 with reason: "contention"), a credential refused, the funded credit hold — is answered here as its own status with {error, reason?, top_up_url?}, and no run is recorded. Everything after it, the model API’s errors included, ends the run as failed with that error.

Poll GET /agent/runs/{run} for its steps, cost and ending, or cancel it with POST /agent/runs/{run}/cancel. A run keeps its thread as the agent route does, and thread_id on the run says which. X-Model-Key is held in memory for the run’s life and never stored. The run uses the API key that started it: deleting that key stops it at its next step, as on the agent route. A run the control plane was executing when it restarted is marked interrupted, with the steps it had taken; it is not resumed. Inspect the desktop before starting another.

Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials. Requires an already-running computer and member+ authority. Set X_MODEL_KEY to your Anthropic key for this request only. Inspect the returned stop/finish reason; HTTP success does not promise task completion. Optional model IDs are Anthropic IDs passed through unchanged.

curl

base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body -X POST "${base_url%/}"'/computers/vm-a1b2c3d4e5f6/agent/runs' \
  -H "Authorization: Bearer $MANDALA_API_KEY" \
  -H "X-Model-Key: ${X_MODEL_KEY:?Set X_MODEL_KEY}" \
  -H "Content-Type: application/json" \
  --data-raw '{"prompt":"Download this month’s invoices into ~/invoices","max_steps":60}'

Python — Mandala SDK 0.16.0

# Python 3.10+: pip install mandala-computer==0.16.0
import json, os
from mandala_computer import Client

with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
    id = "vm-a1b2c3d4e5f6"
    body = json.loads("{\"prompt\":\"Download this month’s invoices into ~/invoices\",\"max_steps\":60}")
    result = client.agent_runs.start(id, body["prompt"], max_steps=body["max_steps"], model_key=os.environ["X_MODEL_KEY"])
    print(result)

TypeScript — Mandala SDK 0.16.0

// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";

const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const modelKey = process.env.X_MODEL_KEY;
if (!modelKey) throw new Error("Set X_MODEL_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const id = "vm-a1b2c3d4e5f6";
const body = {"prompt":"Download this month’s invoices into ~/invoices","max_steps":60};
const result = await client.agentRuns.start(id, {prompt: body.prompt, maxSteps: body.max_steps, modelKey});
console.log(JSON.stringify(result, null, 2));

202 Illustrative successful response.

HTTP 202
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

{
  "id": "run_89abcdef0123456789abcdef",
  "computer_id": "vm-a1b2c3d4e5f6",
  "thread_id": "thr_0123456789abcdef01234567",
  "status": "running",
  "prompt": "Download this month’s invoices into ~/invoices",
  "model": "claude-sonnet-5-5",
  "max_steps": 60,
  "steps": 0,
  "steps_taken": [],
  "created_at": "2026-10-18T09:00:00.000Z",
  "updated_at": "2026-10-18T09:00:00.000Z",
  "ended_at": null
}

401 Platform bearer credential missing.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer

{
  "error": "Authentication required",
  "reason": "missing",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential invalid.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "invalid API key",
  "reason": "invalid",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential revoked.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "API key revoked",
  "reason": "revoked",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

405 Unsupported method on this public path; HEAD returns these headers without a body.

HTTP 405
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
Allow: POST, OPTIONS

{
  "error": "method not allowed",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

Path parameters

idrequiredstring

The computer id.

Headers

X-Model-Keystring

As on POST /computers/{id}/agent. Held in memory while the run goes, and never stored.

Request body

promptrequiredstring

What you want done, in plain language. Required.

systemstring

A system prompt for the model. Requires your own X-Model-Key.

max_cost_usdnumber

As on POST /computers/{id}/agent.

max_stepsinteger

As on POST /computers/{id}/agent: defaults to 20, at most 100.

provider"anthropic" | "openai"

As on POST /computers/{id}/agent.

modelstring

As on POST /computers/{id}/agent.

thread_idstring

A thread to continue, as on POST /computers/{id}/agent.

storeboolean

As on POST /computers/{id}/agent. Default true.

messagesobject[]

As on POST /computers/{id}/agent.

Response

The run, running.

Response fields

idalwaysstring

run_ and twenty-four hex characters.

computer_idalwaysstring
thread_idalwaysstring or null

The thread the run is kept in, once it is: null until a new thread has something in it (a run that has not acted yet, or failed before it did), and when the thread was deleted. Continue it with thread_id on the next run.

statusalways"running" | "completed" | "failed" | "cancelled" | "interrupted"

completed: the loop ended on its own, and result.stop says how — only end_turn means the task was finished. failed: see error. cancelled: stopped by a cancel. interrupted: the control plane restarted while it was going (error.status 503), or the run stopped without its ending being recorded (error.status 500); it was not resumed.

promptalwaysstring

The prompt, at most 32,000 characters of it.

modelalwaysstring

The model the run was started with.

max_stepsalwaysinteger
stepsalwaysinteger

How many actions it has taken so far.

cost_usdnumber

Funded runs: the charge so far, in USD.

resultAgentResult
errorobject
steps_takenalwaysAgentStep[]

Every action so far, in order. Empty in a list.

created_atalwaysstring
updated_atalwaysstring

When it last took a step or changed status.

ended_atalwaysstring or null

When it ended; null while running.

GET/agent/runs

List agent runsmember+

Background runs, newest first. A key confined to a workspace lists that workspace’s. Each run’s steps_taken is empty here; read the run for them. An account keeps its 1000 newest runs that have ended, and every run still going. Members and owners may read them; viewers cannot.

Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials.

curl

base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body "${base_url%/}"'/agent/runs' \
  -H "Authorization: Bearer $MANDALA_API_KEY"

Python — Mandala SDK 0.16.0

# Python 3.10+: pip install mandala-computer==0.16.0
import os
from mandala_computer import Client

with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
    result = client.agent_runs.list()
    print(result)

TypeScript — Mandala SDK 0.16.0

// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";

const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const result = await client.agentRuns.list();
console.log(JSON.stringify(result, null, 2));

200 Illustrative successful response.

HTTP 200
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

{
  "runs": [
    {
      "id": "run_89abcdef0123456789abcdef",
      "computer_id": "vm-a1b2c3d4e5f6",
      "thread_id": "thr_0123456789abcdef01234567",
      "status": "completed",
      "prompt": "Download this month’s invoices into ~/invoices",
      "model": "claude-sonnet-5-5",
      "max_steps": 60,
      "steps": 2,
      "cost_usd": 0.42,
      "result": {
        "steps": 2,
        "cost_usd": 0.42,
        "stop": "end_turn",
        "text": "Saved 3 invoices to ~/invoices.",
        "usage": {
          "input_tokens": 41200,
          "output_tokens": 820,
          "cache_read_tokens": 36000,
          "cache_write_tokens": 3100
        }
      },
      "steps_taken": [],
      "created_at": "2026-10-18T09:00:00.000Z",
      "updated_at": "2026-10-18T09:03:00.000Z",
      "ended_at": "2026-10-18T09:03:00.000Z"
    }
  ],
  "has_more": false
}

401 Platform bearer credential missing.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer

{
  "error": "Authentication required",
  "reason": "missing",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential invalid.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "invalid API key",
  "reason": "invalid",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential revoked.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "API key revoked",
  "reason": "revoked",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

405 Unsupported method on this public path; HEAD returns these headers without a body.

HTTP 405
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
Allow: GET, HEAD, OPTIONS

{
  "error": "method not allowed",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

Query parameters

computer_idstring

Only this computer’s runs.

statusstring

Only runs in this status. Anything else is a 400.

limitinteger

How many to return. Outside 1 to 100 is a 400.

beforestring

The id of the last run of the previous page. One this key cannot see is a 400.

Response

One page of runs.

Response fields

runsalwaysAgentRun[]
has_morealwaysboolean

Whether another page follows; pass the last run’s id as before to get it.

GET/agent/runs/{run}

Read an agent runmember+

The run as it stands: its status, every step so far, its cost on a funded run, and once it has ended, its result (completed) or error (failed, interrupted). Poll it; a few seconds apart is plenty, since a step takes at least that long. A run this key cannot see is a 404.

Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials.

curl

base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body "${base_url%/}"'/agent/runs/run_89abcdef0123456789abcdef' \
  -H "Authorization: Bearer $MANDALA_API_KEY"

Python — Mandala SDK 0.16.0

# Python 3.10+: pip install mandala-computer==0.16.0
import os
from mandala_computer import Client

with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
    run = "run_89abcdef0123456789abcdef"
    result = client.agent_runs.get(run)
    print(result)

TypeScript — Mandala SDK 0.16.0

// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";

const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const run = "run_89abcdef0123456789abcdef";
const result = await client.agentRuns.get(run);
console.log(JSON.stringify(result, null, 2));

200 Illustrative successful response.

HTTP 200
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

{
  "id": "run_89abcdef0123456789abcdef",
  "computer_id": "vm-a1b2c3d4e5f6",
  "thread_id": "thr_0123456789abcdef01234567",
  "status": "completed",
  "prompt": "Download this month’s invoices into ~/invoices",
  "model": "claude-sonnet-5-5",
  "max_steps": 60,
  "steps": 2,
  "cost_usd": 0.42,
  "result": {
    "steps": 2,
    "cost_usd": 0.42,
    "stop": "end_turn",
    "text": "Saved 3 invoices to ~/invoices.",
    "usage": {
      "input_tokens": 41200,
      "output_tokens": 820,
      "cache_read_tokens": 36000,
      "cache_write_tokens": 3100
    }
  },
  "steps_taken": [
    {
      "n": 1,
      "cost_usd": 0.21,
      "tool": "computer",
      "action": "left_click",
      "detail": "clicked (512, 300)"
    },
    {
      "n": 2,
      "cost_usd": 0.42,
      "tool": "bash",
      "detail": "ls ~/invoices"
    }
  ],
  "created_at": "2026-10-18T09:00:00.000Z",
  "updated_at": "2026-10-18T09:03:00.000Z",
  "ended_at": "2026-10-18T09:03:00.000Z"
}

401 Platform bearer credential missing.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer

{
  "error": "Authentication required",
  "reason": "missing",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential invalid.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "invalid API key",
  "reason": "invalid",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential revoked.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "API key revoked",
  "reason": "revoked",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

405 Unsupported method on this public path; HEAD returns these headers without a body.

HTTP 405
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
Allow: GET, HEAD, OPTIONS

{
  "error": "method not allowed",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

Path parameters

runrequiredstring

A run id from POST /computers/{id}/agent/runs (run_ followed by 24 lowercase hex characters).

Response

The run.

Response fields

idalwaysstring

run_ and twenty-four hex characters.

computer_idalwaysstring
thread_idalwaysstring or null

The thread the run is kept in, once it is: null until a new thread has something in it (a run that has not acted yet, or failed before it did), and when the thread was deleted. Continue it with thread_id on the next run.

statusalways"running" | "completed" | "failed" | "cancelled" | "interrupted"

completed: the loop ended on its own, and result.stop says how — only end_turn means the task was finished. failed: see error. cancelled: stopped by a cancel. interrupted: the control plane restarted while it was going (error.status 503), or the run stopped without its ending being recorded (error.status 500); it was not resumed.

promptalwaysstring

The prompt, at most 32,000 characters of it.

modelalwaysstring

The model the run was started with.

max_stepsalwaysinteger
stepsalwaysinteger

How many actions it has taken so far.

cost_usdnumber

Funded runs: the charge so far, in USD.

resultAgentResult
errorobject
steps_takenalwaysAgentStep[]

Every action so far, in order. Empty in a list.

created_atalwaysstring
updated_atalwaysstring

When it last took a step or changed status.

ended_atalwaysstring or null

When it ended; null while running.

POST/agent/runs/{run}/cancel

Cancel an agent runmember+

Stops a running run where it is: the model call in flight is abandoned and no further action is taken. What it already did to the desktop stands, and so does what it cost. The run answers as cancelled. Cancelling a run that already ended answers it as it is, unchanged. A run this key cannot see is a 404.

Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials.

curl

base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body -X POST "${base_url%/}"'/agent/runs/run_89abcdef0123456789abcdef/cancel' \
  -H "Authorization: Bearer $MANDALA_API_KEY"

Python — Mandala SDK 0.16.0

# Python 3.10+: pip install mandala-computer==0.16.0
import os
from mandala_computer import Client

with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
    run = "run_89abcdef0123456789abcdef"
    result = client.agent_runs.cancel(run)
    print(result)

TypeScript — Mandala SDK 0.16.0

// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";

const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const run = "run_89abcdef0123456789abcdef";
const result = await client.agentRuns.cancel(run);
console.log(JSON.stringify(result, null, 2));

200 Illustrative successful response.

HTTP 200
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17

{
  "id": "run_89abcdef0123456789abcdef",
  "computer_id": "vm-a1b2c3d4e5f6",
  "thread_id": "thr_0123456789abcdef01234567",
  "status": "completed",
  "prompt": "Download this month’s invoices into ~/invoices",
  "model": "claude-sonnet-5-5",
  "max_steps": 60,
  "steps": 2,
  "cost_usd": 0.42,
  "result": {
    "steps": 2,
    "cost_usd": 0.42,
    "stop": "end_turn",
    "text": "Saved 3 invoices to ~/invoices.",
    "usage": {
      "input_tokens": 41200,
      "output_tokens": 820,
      "cache_read_tokens": 36000,
      "cache_write_tokens": 3100
    }
  },
  "steps_taken": [
    {
      "n": 1,
      "cost_usd": 0.21,
      "tool": "computer",
      "action": "left_click",
      "detail": "clicked (512, 300)"
    },
    {
      "n": 2,
      "cost_usd": 0.42,
      "tool": "bash",
      "detail": "ls ~/invoices"
    }
  ],
  "created_at": "2026-10-18T09:00:00.000Z",
  "updated_at": "2026-10-18T09:03:00.000Z",
  "ended_at": "2026-10-18T09:03:00.000Z"
}

401 Platform bearer credential missing.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer

{
  "error": "Authentication required",
  "reason": "missing",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential invalid.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "invalid API key",
  "reason": "invalid",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

401 Platform bearer credential revoked.

HTTP 401
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
WWW-Authenticate: Bearer error="invalid_token"

{
  "error": "API key revoked",
  "reason": "revoked",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

405 Unsupported method on this public path; HEAD returns these headers without a body.

HTTP 405
Content-Type: application/json
X-Request-ID: 3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17
Allow: POST, OPTIONS

{
  "error": "method not allowed",
  "request_id": "3f2c9a1e-7b4d-4e8a-9c61-0d5f2b8e4a17"
}

Path parameters

runrequiredstring

A run id from POST /computers/{id}/agent/runs (run_ followed by 24 lowercase hex characters).

Response

The run.

Response fields

idalwaysstring

run_ and twenty-four hex characters.

computer_idalwaysstring
thread_idalwaysstring or null

The thread the run is kept in, once it is: null until a new thread has something in it (a run that has not acted yet, or failed before it did), and when the thread was deleted. Continue it with thread_id on the next run.

statusalways"running" | "completed" | "failed" | "cancelled" | "interrupted"

completed: the loop ended on its own, and result.stop says how — only end_turn means the task was finished. failed: see error. cancelled: stopped by a cancel. interrupted: the control plane restarted while it was going (error.status 503), or the run stopped without its ending being recorded (error.status 500); it was not resumed.

promptalwaysstring

The prompt, at most 32,000 characters of it.

modelalwaysstring

The model the run was started with.

max_stepsalwaysinteger
stepsalwaysinteger

How many actions it has taken so far.

cost_usdnumber

Funded runs: the charge so far, in USD.

resultAgentResult
errorobject
steps_takenalwaysAgentStep[]

Every action so far, in order. Empty in a list.

created_atalwaysstring
updated_atalwaysstring

When it last took a step or changed status.

ended_atalwaysstring or null

When it ended; null while running.