---
title: "Read what a run actually did, step by step"
method: GET
path: "/v1/runs/{run_id}/log"
tags: ["runs"]
---

# Read what a run actually did, step by step

`GET /v1/runs/{run_id}/log`

Scope: `runs:read`. Free. **This is the supported way to read a finished run's full trajectory or its final answer.** `GET /v1/runs/{run_id}/events` streams the same source over SSE, which suits watching a run live but makes inspecting a completed one awkward; `result.summary` on the run object is only the last 2000 characters.

Each step arrives assembled: the model's reasoning already unwrapped from its `<cua-section>` markup (`analysis`, `next_action`, `grounded_action`, `action_result`), then every action with its coordinates and outcome, plus what the step cost and the `screenshot_index` of the frame the agent saw before acting. No parsing required. Pass `include_events=true` to also get the raw events, nested inside the step they belong to.

**Read `log_complete` before concluding anything from an absence.** The event log is BEST EFFORT and is not written inside the run's transaction, so a step can execute and be billed while its log rows are lost. When `log_complete` is false, `steps_completed` is the authoritative count and a step missing here did not necessarily fail to happen.

Page with `after_step`. `lifecycle` is returned whole on every page because it is small and usually contains the reason the run stopped.

Errors: 404 RUN_NOT_FOUND for an unknown or cross-tenant run (ownership is checked first, so a run you do not own is never revealed as an empty log). 503 DB_UNAVAILABLE with Retry-After when the event store is failing.

## Query parameters

- `limit` integer
- `after_step` integer
- `include_events` boolean

## Response `200`

A page of assembled steps, plus run-level lifecycle events.

- ListRunLogResponse — The assembled log for one run: one record per agent step, plus the run-level events that belong to no step.
  - `object` 'list', required
  - `data` RunStepDetail[], required
    - `step` integer
    - `attempt` integer — A run that was reaped and re-claimed restarts its step counter at 1, so `step` alone is not a unique address within a run. Pair it with `attempt`.
    - `started_at` string, date-time, nullable
    - `ended_at` string, date-time, nullable
    - `analysis` string, nullable — The model's own account of what it was looking at, unwrapped from its <cua-section> markup. Any reasoning section may be absent.
    - `next_action` string, nullable
    - `grounded_action` string, nullable
    - `action_result` string, nullable — What the screen actually did in response, as the agent recorded it (e.g. "Double-clicked at (70, 527)" or "no screen change detected"). The most useful field for diagnosing a stuck run: it is where "the click landed but nothing happened" becomes visible.
    - `actions` RunAction[] — Every tool call in this step, in order, with coordinates and outcome.
      - `seq` integer, required
      - `tool` string, required — The action/tool name invoked.
      - `args` object — Arguments as executed, including coordinates.
      - `ok` boolean, nullable — The outcome the agent observed. null when the run ended before the result was recorded: a step cut off by a deadline or a crash has a call with no result, and reporting that as a failure would be a guess. An action in flight is genuinely different from one that failed.
      - `error` string, nullable — Present only when the agent reported a failure reason.
    - `credits_charged` integer
    - `cost_cents` integer
    - `screenshot_index` integer, nullable — Flat index into GET /v1/runs/{run_id}/screenshots for the frame the agent saw BEFORE acting, when one was captured for this step.
    - `error` string, nullable
    - `events` RunEvent[], nullable — The raw events this step was folded from. Present only with include_events=true. Nested under the step rather than returned as a parallel array, so a page boundary can never split a step from its events.
      - `seq` integer — Monotonic cursor; pass via Last-Event-ID or ?after= to resume an SSE stream.
      - `type` string — Event type (e.g. status, step, awaiting_human, done).
      - `data` object
      - `created_at` string, date-time, nullable
  - `lifecycle` RunEvent[] — Events that belong to the run rather than to any step: queued/running/done transitions, run-level errors, awaiting-human pauses. A step-shaped view would silently drop these, and they are usually the ones that explain why a run stopped. Returned whole on every page because it is small.
    - `seq` integer — Monotonic cursor; pass via Last-Event-ID or ?after= to resume an SSE stream.
    - `type` string — Event type (e.g. status, step, awaiting_human, done).
    - `data` object
    - `created_at` string, date-time, nullable
  - `has_more` boolean
  - `next_after_step` integer, nullable — Cursor: pass as after_step to continue. null when the log is complete.
  - `live` boolean — True when the run is still going, so the log will grow.
  - `steps_completed` integer — Authoritative count from the run record: how many steps actually ran. Trust this over the length of `data`.
  - `steps_logged` integer — How many steps this log could reconstruct any evidence for.
  - `log_complete` boolean — False when the log is missing steps the run is known to have executed. The event log is BEST EFFORT: appends are not part of the run's transaction, so a step can execute and be billed while its log rows are lost. When this is false, `steps_completed` is authoritative and the absence of a step here is NOT evidence that it did not happen.
  - `request_id` string

## Other responses

- `400` — Invalid request body or parameters.
- `401` — Missing, invalid, or revoked API key. Pass `X-API-Key: sk-coasty-live-...` (or test).
- `403` — API key lacks the required scope or tier-feature is unavailable on the caller's plan.
- `404` — Resource not found in this key's namespace.
- `409` — The resource state conflicts with this operation.
- `413` — The request body exceeds the endpoint limit.
- `422` — The JSON shape is valid but one or more values violate the endpoint contract.
- `429` — Rate or concurrency limit exceeded.
- `500` — Unexpected server error. Retry with exponential backoff.
- `502` — An upstream dependency returned an invalid response.
- `503` — A required service is temporarily unavailable.
- `504` — An upstream dependency timed out.

---

[API](https://skmtc.net/coasty/apis/coasty-public-api.md) · [All operations](https://skmtc.net/coasty/apis/coasty-public-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/coasty/coasty-public-api/revisions/f50d93b0d8a1/schema)
