Coasty/

Developer API

Tasks

Live SSE stream with Last-Event-ID replay

Streaming events

Streaming events, at a glance

Live SSE stream with Last-Event-ID replay

GET /v1/runs/{id}/events
Run events are ordered SSE records. Persist the last event ID and reconnect from that cursor after a network interruption.

GET /v1/runs/{id}/events returns a Server-Sent Events stream so you can follow a run as it happens, instead of polling. Each event has a type and a numeric id (the sequence number). If your connection drops, reconnect and replay everything you missed by sending the last sequence you saw as a Last-Event-ID header, or as the ?after= query parameter. The stream is ordered and at least once: deduplicate by run ID plus sequence, and atomically persist the last processed sequence with your side effects because a disconnect before that commit can replay an event. Successfully persisted frames remain replayable, but intermediate text, reasoning, tool, step, billing, pause/resume, status, and error appends are best-effort and can be absent after a transient storage failure. Terminal status and done are transactionally admitted with Agent Run terminal state. Always reconcile current state with GET /v1/runs/{id}.

import os, httpx

BASE = "https://coasty.ai/v1"
HEADERS = {"X-API-Key": os.environ["COASTY_API_KEY"]}
run_id = "7a1b2c3d-4e5f-4678-9abc-def012345678"
last_seq = 0  # persist this so a reconnect can replay

# httpx streams the SSE body line by line. Reconnect with Last-Event-ID.
with httpx.stream(
    "GET",
    f"{BASE}/runs/{run_id}/events",
    headers={**HEADERS, "Last-Event-ID": str(last_seq)},
    timeout=None,
) as resp:
    event_type = "message"
    for line in resp.iter_lines():
        if line.startswith("id:"):
            last_seq = int(line[3:].strip())
        elif line.startswith("event:"):
            event_type = line[6:].strip()
        elif line.startswith("data:"):
            data = line[5:].strip()
            print(event_type, data)
            if event_type == "done":
                break
EventMeaning
statusThe run moved to a new status (running, awaiting_human, succeeded, etc.).
textA chunk of the agent's natural-language narration.
reasoningA chunk of the model's private reasoning, if exposed.
tool_callThe agent invoked a tool (a click, a keypress, a navigation).
tool_resultThe result of the most recent tool call.
awaiting_humanThe run paused and is waiting for a human to take over.
resumedControl was handed back after a human takeover.
stepA full agent step completed; carries steps_completed.
billingIncremental billing update (credits_charged, cost_cents).
errorA non-fatal or fatal error occurred during the run.
doneTerminal event. The stream closes after this is sent.
timeoutThe alternative terminal frame: the stream hit its own maximum duration and closed with data {"reason":"stream_max_duration"}. It does not mean the run ended. Reconnect with Last-Event-ID, or reconcile with GET /v1/runs/{id}.
The stream has two terminal frames, not one. The ordinary ending is event: done. The other is event: timeout carrying {"reason":"stream_max_duration"}, sent when the connection reaches its own maximum duration. It says nothing about the run, which is very likely still going. A client that waits only for done will hang on a long run. On timeout, reconnect with Last-Event-ID to continue from your cursor, or fall back to GET /v1/runs/{id}. The same two endings apply to GET /v1/workflows/runs/{id}/events.

Reading a finished run

GET /v1/runs/{id}/log is the supported way to read a run's full trajectory. SSE is built for watching a run live; replaying a stream to inspect one that already ended is awkward and lossy in a way that is easy to get wrong. The log endpoint reads the same source and returns it as ordinary paginated JSON, one record per agent step, already assembled. It needs the runs:read scope.

Each step arrives with the model's reasoning already unwrapped from its internal <cua-section> markup, split into analysis, next_action, and grounded_action. Alongside them, actions lists every tool call in order with its arguments and outcome, plus what the step cost and which screenshot the agent was looking at. No parsing on your side.

Query parameterDefaultContract
limit50Steps per page, 1 to 200. Out of range is 400 INVALID_LIMIT with actual, min, and max in the body.
after_step0Cursor. Pass back the response's next_after_step to continue. A negative value is 400 INVALID_EVENT_CURSOR.
include_eventsfalseAlso return the raw events each step was folded from, nested inside data[].events so a page boundary can never split a step from its events.
Response fieldWhat it is
data[]One record per step: step, attempt, started_at, ended_at, analysis, next_action, grounded_action, actions[], credits_charged, cost_cents, screenshot_index, and error.
data[].actions[]seq, tool, args, ok, and error. ok is null when the run ended before the result was recorded: an action in flight is genuinely different from one that failed.
lifecycle[]Run-level rather than step-level events: queued/running/done transitions, run-level errors, awaiting-human pauses. Returned whole on every page because it is small and usually contains the reason the run stopped.
has_moreMore pages exist. next_after_step is the cursor and is null once the log is exhausted.
liveThe run is still going, so the log will grow. Poll or switch to the SSE stream.
steps_completedAuthoritative count from the run record: how many steps actually ran.
steps_loggedHow many steps this log could reconstruct any evidence for.
log_completeFalse when the log is missing steps the run is known to have executed. Read this before concluding anything from an absence.
Read log_complete first. The event log is best effort and is deliberately not written inside the run's transaction, so a step can execute and bill while its log rows are lost. When log_complete is false, steps_completed is the authoritative count and a step missing from data[] did not necessarily fail to happen. Never bill, reconcile, or alert off steps_logged.

Pair the log with a run's result.summary, which is only the last 2,000 characters of the trajectory, and with GET /v1/runs/{id}/screenshots when you need the exact frames the agent saw. Errors on this endpoint are 404 RUN_NOT_FOUND for an unknown, cross-tenant, or wrong-key-mode id (never an empty log, which would confirm the id exists) and 503 DB_UNAVAILABLE with Retry-After when the event store is briefly unreachable.

API reference — Coasty Computer Use API | Coasty - AI Computer-Use Agent