Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Meza-dev/Ghostly/llms.txt

Use this file to discover all available pages before exploring further.

The GET /v1/runs/:id/events/stream endpoint delivers a live Server-Sent Events (SSE) stream for a single run. As soon as you connect, the server replays the current run status and any assist events that have already been persisted (catch-up), then forwards new events in real time until the run reaches a terminal state. The stream is also sent a heartbeat ping every 15 seconds to keep the connection alive and allow proxy timeouts to be detected.

Authentication

EventSource (the browser’s native SSE client) does not support custom request headers. Authentication is therefore performed via a JWT query parameter rather than the X-Api-Key header used by other endpoints.
Append ?token=<jwt> to the URL. Obtain a JWT by calling POST /v1/auth/login with your credentials.

Path Parameters

id
string (UUID)
required
The run ID returned by POST /v1/run.

Query Parameters

token
string
required
A valid JWT for the authenticated user.

Transport

  • HTTP method: GET
  • Content-Type: text/event-stream
  • Encoding: UTF-8
  • Message format: each SSE data field contains a JSON-encoded event object

Event Types

Every message sent over the stream is a JSON object with a kind discriminant.

Status Event

Sent at connection time (reflecting the current state) and whenever the run’s lifecycle status changes.
kind
"status"
Identifies this message as a lifecycle status update.
status
"running" | "pass" | "fail"
The current status of the run at the moment the event was emitted.
at
string (ISO 8601)
Timestamp when the status transition occurred.

Assist Event

Emitted by the AI-assisted pipeline. Each event describes one step of the planner’s internal reasoning, step execution, healing, or memory operations.
kind
"assist"
Identifies this message as an AI pipeline event.
type
AssistEventType
One of the following values:
TypeDescription
reconAccessibility-tree snapshot captured for the current page
plan_chunkA batch of planned steps received from the LLM
loop_stateGeneral loop metadata (horizon count, elapsed ms, etc.)
horizon_startA new planning horizon has begun
horizon_endThe current horizon finished (success or exhaustion)
victory_checkThe planner evaluated victory conditions
memory_hitA matching memory was found and will seed this run
memory_missNo matching memory found for this goal/project
step_startA single step is about to be executed
step_successA step completed successfully
step_failureA step failed (may trigger healing)
heal_startThe healer is attempting to recover a failed step
heal_actionThe healer selected a corrective action
heal_successHealing succeeded; execution continues
heal_failureHealing exhausted all attempts for this step
run_endThe run has finished (terminal event before stream closes)
seq
number
Monotonically increasing sequence number within the run.
at
string (ISO 8601)
Timestamp when the event was emitted.
stepIndex
number
Zero-based index of the step this event is associated with. Present for step-scoped types (step_start, step_success, step_failure, heal_start, heal_action, heal_success, heal_failure).
payload
object
Event-specific data. Shape varies by type. For run_end, check for payload.cancelled and payload.staleAfterRestart to distinguish user-cancelled and orphaned runs.

End Event

Sent by the server immediately before closing the stream when a non-running status is reached (including catch-up replays of already-finished runs).
kind
"end"
Signals that the stream is about to close. Clients should call es.close() upon receiving this event.
at
string (ISO 8601)
Timestamp of the stream close.

Heartbeat (ping)

A SSE ping event with a Unix timestamp string in the data field is sent every 15 seconds while the run is active. You do not need to handle it explicitly — it exists to keep connections alive through proxies and load balancers.

Stream Lifecycle

  1. Connection — server sends the current status event.
  2. Catch-up — all assist events already persisted for the run are replayed in sequence order.
  3. If the run has already finished — an end event is sent and the stream closes immediately.
  4. If the run is still active — live events are forwarded as they are emitted by the background execution engine.
  5. Termination — the stream closes after a run_end assist event followed by a final status event ("pass" or "fail"), or when the client disconnects.

Examples

import { GhostlyClient } from "@ghostly-io/client";

const client = new GhostlyClient({
  baseUrl: "https://api.ghostly.dev",
  apiKey: "gly_YOUR_API_KEY",
});

// 1. Start the run
const { id } = await client.startRun({
  project: "my-app",
  baseUrl: "https://staging.example.com",
  steps: [
    { action: "goto", url: "https://staging.example.com/login" },
    { action: "click", selector: "button[type=submit]" },
  ],
});

// 2. Obtain a JWT via POST /v1/auth/login, then open the SSE stream
const jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
const url = client.runEventsUrl(id, jwtToken);
const es = new EventSource(url);

es.onmessage = (e) => {
  const event = JSON.parse(e.data);
  console.log(event);

  if (event.kind === "status" && event.status !== "running") {
    // Run finished — "pass" or "fail"
    console.log("Final status:", event.status);
    es.close();
  }
};

es.onerror = (err) => {
  console.error("SSE connection error:", err);
  es.close();
};
If you are running server-side code (CLI, CI scripts, or test runners) and do not need real-time updates, use client.waitForRun(id) instead. It polls GET /v1/runs/:id at a configurable interval until the run leaves "running" status, without requiring a JWT or an open HTTP connection.
The stream connection is automatically closed by the server once the run reaches "pass" or "fail". Always implement an onerror handler and call es.close() on your side as well to avoid connection leaks.

Build docs developers (and LLMs) love