POST /v1/tasks/{task_id}/runs runs a task once with the values you supply and waits for it to finish. There is no queue and no job id to poll — the response is the whole run.
A run executes for real. The tools call your endpoints with your secrets, the MCP servers spend your credits, and the model spends your tokens on your own provider key. A task that books a slot, charges a card or sends a message will do so. This is true of the Run panel in the dashboard too.
Running a task needs the editor role. Reading its history does not.
The request blocks for as long as the run takes, up to the task’s timeout_seconds (default 120, max 600). Set your HTTP client’s read timeout above it — the Python SDK defaults to 30 seconds, which will abandon a run that is still perfectly healthy.

What comes back

billing_status is skipped when nothing reached a provider — a missing required variable, a missing provider key — which is not the same as spend of zero. It is unpriceable when the run happened but its model is missing from the catalog, so the cost is quarantined rather than reported as zero.

Reading attempts and steps_used

These two numbers only make sense together, and reading steps_used alone will mislead you. A step is one model → tools → model round, not one tool call. Four tools called in a single reply cost one step. A run that ends without calling submit_result is re-prompted rather than failed — up to twice, so attempts is 1, 2 or 3. And each re-prompt starts the step budget over. So attempts: 3 means two earlier attempts ended with no result — most often because they used every one of their steps — and the third one recovered. That run may have used up to three times the rounds steps_used shows. steps_used reports the busiest attempt’s count, not the total, because the busiest attempt’s count is the number max_steps actually governs. Summing them would report step_limit for two well-behaved attempts in a row. Which means timeout_seconds, not max_steps, is what really bounds a run: three attempts of 25 rounds each is 75 rounds of work, and only the clock stops it. A successful run’s last round is the submit_result call itself, so a task that calls no other tool still reports steps_used: 1. In the dashboard, the Run panel shows this as 4 of 25 on one attempt and 12 of 25 ×3 on three.

The trace

trace is everything the model did, in order. Two kinds of entry:
  • kind: "tool" is a call the model made. name is the tool as the model called it, which for an MCP tool is the namespaced name. args and result are parsed when they are JSON and kept as text when they are not. ms is the time from the call to its output.
  • kind: "message" is something the model wrote. name is assistant, the text is in result, and ms is null because a message is written progressively and has no such pair of moments.
Two things are done to it before it is stored, and both are visible:
  • Workspace secret values are masked. Redaction happens before the row is written, not before it is rendered — the trace is readable by everyone in the workspace, so a credential that reached it once would already have leaked.
  • A field longer than 4,000 characters is cut and the entry’s truncated flag is set to true, so a short trace is never mistaken for a complete one.
The trace holds at most 200 entries. A run longer than that keeps the first 200; steps_used still reports what actually happened.

History

GET /v1/tasks/{task_id}/runs returns this task’s runs, newest first, each with its full output, trace and cost. GET /v1/tasks/{task_id}/runs/{run_id} returns one.
The list is the standard page shape — items, has_more, limit, offset — with a default limit of 50 and a maximum of 200. See pagination. GET /v1/tasks carries last_run on every task, so a list page can say “last run: failed, step_limit” without a request per row. Deleting a task does not delete its runs. They keep the task’s name and their task_id goes null.
Task runs are not in the Observability charts. Those are built on calls and conversations, and a task is neither. A task’s own history is the only place its runs live, and its spend shows only there and on an email batch page.

In the dashboard

The Run panel at the bottom of the task editor gives one field per declared variable, a Run button, and the finished run underneath: a status badge, how long it took, steps of max with the attempt count beside it, the cost and the token total, then the result field by field — with null rendered as null — not found rather than a blank cell — and an expandable list of everything the model did. A run uses the saved task. If the editor has unsaved changes, the panel says so; there is no “run my draft” here, because there is no draft. Each row of an email batch links to the run that drafted it, rendered the same way. That is where “why did it write that” gets answered.

Next

Errors and limits

Every error.type, whose problem it is, and the fix.

Email outbound

Run a task once per row of a CSV and review what it wrote.