---
title: "List agent runs"
method: GET
path: "/agent/runs"
tags: ["Agents"]
---

# List agent runs

`GET /agent/runs`

## Request

- Base URL: `https://app.mandala.computer/api/v1`
- URL: `https://app.mandala.computer/api/v1/agent/runs`
- Auth: HTTP bearer

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.

Requires the `member` role or higher.

## Query parameters

- `computer_id` string
- `status` 'running' | 'completed' | 'failed' | 'cancelled' | 'interrupted'
- `limit` integer
- `before` string

## Response `200`

One page of runs.

- AgentRunList
  - `runs` AgentRun[], required
    - `id` string, required: `run_` and twenty-four hex characters.
    - `computer_id` string, required
    - `thread_id` string, nullable, required: 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.
    - `status` 'running' | 'completed' | 'failed' | 'cancelled' | 'interrupted', required: `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.
    - `prompt` string, required: The prompt, at most 32,000 characters of it.
    - `model` string, required: The model the run was started with.
    - `max_steps` integer, required
    - `steps` integer, required: How many actions it has taken so far.
    - `cost_usd` number: Funded runs: the charge so far, in USD.
    - `result` AgentResult
      - `cost_usd` number: Cumulative AI credit charge in USD, present on funded runs only.
      - `steps` integer, required: How many actions the loop took — a count; the actions themselves are `steps_taken`.
      - `stop` string, required: 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.
      - `text` string, required: The model’s closing text — its answer, or why it could not get there.
      - `usage` AgentUsage, required
        - `input_tokens` integer, required: Input tokens, the cache reads and writes below included.
        - `output_tokens` integer, required
        - `cache_read_tokens` integer, required
        - `cache_write_tokens` integer, required: Provider-reported cache writes; zero only when absent. OpenAI reads input_tokens_details.cache_write_tokens.
      - `thread_id` string: The thread this exchange was kept in; send it as `thread_id` to continue. Absent with `store: false`.
      - `steps_taken` AgentStep[]: 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.
        - `cost_usd` number: Cumulative funded run charge in USD. Multiple tools in one reply repeat the same total; do not sum these fields.
        - `credits_remaining_usd` number: Available account AI credits after this model call; may be negative after an overrun. Omitted if the post-debit balance lookup is unavailable.
        - `n` integer, required: Its position in the run, from 1.
        - `tool` string, required: The tool the model reached for, e.g. `computer` or `bash`.
        - `action` string: The provider-native action within it, e.g. `left_click`, `zoom` or OpenAI `click`. Absent for `bash`.
        - `detail` string: What the platform did with it, in one line.
        - `error` string: Present when an action failed or was refused. On an Anthropic run the model is told and the loop goes on. On an OpenAI run a failed batch or `bash` command ends the run as a failure, after the actions the desktop acknowledged; input is not replayed.
    - `error` object
      - `error` string, required: What went wrong, in a sentence.
      - `status` integer, required: The status the agent route would have answered it with.
      - `reason` string: The one-word classification, where there is one — see `Error`.
      - `top_up_url` string: Where to add credits, when the run failed for want of them.
    - `steps_taken` AgentStep[], required: Every action so far, in order. Empty in a list.
      - `cost_usd` number: Cumulative funded run charge in USD. Multiple tools in one reply repeat the same total; do not sum these fields.
      - `credits_remaining_usd` number: Available account AI credits after this model call; may be negative after an overrun. Omitted if the post-debit balance lookup is unavailable.
      - `n` integer, required: Its position in the run, from 1.
      - `tool` string, required: The tool the model reached for, e.g. `computer` or `bash`.
      - `action` string: The provider-native action within it, e.g. `left_click`, `zoom` or OpenAI `click`. Absent for `bash`.
      - `detail` string: What the platform did with it, in one line.
      - `error` string: Present when an action failed or was refused. On an Anthropic run the model is told and the loop goes on. On an OpenAI run a failed batch or `bash` command ends the run as a failure, after the actions the desktop acknowledged; input is not replayed.
    - `created_at` string, required
    - `updated_at` string, required: When it last took a step or changed status.
    - `ended_at` string, nullable, required: When it ended; `null` while running.
  - `has_more` boolean, required: Whether another page follows; pass the last run’s id as `before` to get it.

## Other responses

- `400`: The request was malformed — a bad resolution string, a missing `command`, a `{pid}` or `{window}` that is not one. It is also what a well-formed request gets when the thing it names cannot be acted on and never will be — a restore onto a computer that is gone, for instance. Either way it is final: the message says what is wrong, and sending it again unchanged answers the same. That is the whole difference from a `503`, which means ask again shortly.
- `401`: Platform authentication required or refused: reason is missing, invalid, or revoked, with WWW-Authenticate: Bearer (invalid_token for invalid/revoked).
- `403`: The credential is recognized but its holder lacks the required role or membership, or the holder/account is suspended, or the holder has not verified their email address. An ordinary denial for too low a role may carry no `reason`. The email one carries none either, and its message begins `Verify your email before`: it answers a create, a clone of a computer or a snapshot, a new API key and a new SSH key, and is cleared by verifying the address in the dashboard. `reason: "revoked"` marks authority that is gone: a suspended account or a lost membership (refused as the request arrives, or if it happens while the request is in flight), or a role that was lowered while the request was in flight. The message identifies the refusal; replacing a valid key alone does not restore authority, and sending the same call again does not help.
- `405`: This public path exists but does not accept that method. Allow lists only its public methods, with HEAD for GET and metadata OPTIONS.
- `429`: You are asking faster than your plan allows. `Retry-After` says how many seconds to wait, and every metered response — anything past authentication and the re-check that your credential’s authority still holds — carries `RateLimit-Remaining`, so you do not have to be refused to find out. A `401`, a `403` from that re-check, an `OPTIONS` answer and an unexpected `500` carry none. The budget is spent in proportion to what a call costs rather than per request. What a call costs, in budget units, by kind (each operation’s exact figure is its `x-rate-cost` in the OpenAPI document): 1 to read one thing; 5 for a listing that spans the fleet, and 5 for a call into a running guest such as a screenshot or an exec; 10 for a power change and 10 to move file bytes; 25 for `GET /usage`, `GET /credits`, creating a public URL, and creating, rotating or testing a webhook; 50 to capture a snapshot; 100 to create, clone or restore a computer, and 100 to start an agent run, whose actions are then charged as they happen. The account-wide budget, as what an idle caller may spend at once and what refills each minute: No plan 1200 at once and 1200 a minute; Solo 4800 at once and 4800 a minute; Studio 12000 at once and 12000 a minute; Swarm 21600 at once and 21600 a minute. A single API key gets half of either. A `401` for a missing or unusable credential costs nothing: it is refused before anything is metered. A few refusals at the door cost one unit: a `403` because the role is too low or the route refuses an API key confined to a workspace, a `404` for a path that matches nothing, a `405`, and an `Idempotency-Key` answered from its stored result or still in progress. Everything else is charged in full when it is admitted, before it is carried out, so a refusal after that has spent the whole cost: a `400`, `402` or `409` from the route, a `403` its handler sends (on most routes, a suspended account included), and a model provider’s `401` on an agent run. The budget belongs to the account and is shared across everything on it, and any single API key may spend at most half of it. `RateLimit-Limit` is whichever of those two is binding on the request that carried it — usually the per-key half, and the account-wide figure when other keys have already spent more of it than this one has. All three `RateLimit-*` headers describe the same one, so `limit` minus `remaining` is what has been spent against it. A few `429`s are not about this budget, say so in their message, and can carry no `Retry-After`: a model provider’s rate limit relayed on an agent run. The cap on forks of one snapshot waiting for their secrets sends its own bounded expiry-based `Retry-After` with `reason: "contention"` (see `POST /snapshots/{id}/clone`).
- `500`: The application could not finish this: an unexpected exception, a build that broke, or a storage fault. Application refusals carry the request correlation ID; unexpected internal details are not returned.

---

[API](https://skmtc.dev/mandala/apis/mandala-computer-api.md) · [All operations](https://skmtc.dev/mandala/apis/mandala-computer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mandala/apis/mandala-computer-api/revisions/2afbb87fa703?raw)
