---
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`

## Request

- Base URL: `https://coasty.ai`
- URL: `https://coasty.ai/v1/runs/{run_id}/log`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer
- TypeScript: <https://skmtc.dev/coasty/apis/coasty-public-api/docs/v1/runs/:run_id/log/get.ts> (a function with typed inputs; its response is checked against the schema)

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.dev/coasty/apis/coasty-public-api.md) - [All operations](https://skmtc.dev/coasty/apis/coasty-public-api/llms.txt) - [OpenAPI document](https://skmtc.dev/coasty/apis/coasty-public-api/revisions/f50d93b0d8a1?raw)
