emit.run

Space Stream

Real-time event stream for all jobs in a space

Space Stream

GET /api/v1/realtime/spaces/:spaceId

Scope: jobs:read

Connect to a space-wide stream. You receive events for all jobs in the space: creation, status changes, progress, completions, failures, and kills.

The stream sends a connected event immediately on connect. For full-scope clients, this includes live aggregate counts and reconnect metadata.

ParamRequiredDescription
tokenYesAPI key with jobs:read or higher. Must be scoped to this space if the key is space-scoped.
sinceNoLast seq you successfully processed. Used for reconnect gap detection.
streamInstanceIdNoLast stream instance ID from connected. Used to detect actor restarts on reconnect.
const ws = new WebSocket(
  `wss://emit.run/api/v1/realtime/spaces/${spaceId}?token=${apiKey}`
);

ws.onmessage = (e) => {
  const event = JSON.parse(e.data);
  console.log(`[${event.jobName}] ${event.type} → ${event.status}`);
};
async def watch_space(space_id: str, api_key: str):
    uri = f"wss://emit.run/api/v1/realtime/spaces/{space_id}?token={api_key}"

    async with websockets.connect(uri) as ws:
        async for message in ws:
            event = json.loads(message)
            print(f"[{event['jobName']}] {event['type']}{event['status']}")

asyncio.run(watch_space("01JLQX...", "emit_YOUR_KEY"))

The space stream uses the same event shape as the job stream, with the addition of jobName and stream metadata for robust reconnect behavior. Requires jobs:read or higher — so events always include full data.


Stream metadata

FieldWhere it appearsMeaning
seqconnected + all space eventsMonotonic sequence number for this stream instance.
streamInstanceIdconnectedIdentifier for the current stream actor instance. Changes after actor restart.
missedconnectedtrue when your reconnect cursor does not match server state (missed events or actor restart).
countsconnected + count-changing status eventsServer-side aggregate counts by status (scheduled, pending, delivered, running, completed, failed, dead, killed).
previousStatusstatus-transition eventsPrior status before this transition (useful for exact counter diffs).

Reconnect strategy

For dashboards, store seq and streamInstanceId from incoming messages and send them back on reconnect:

let lastSeq: number | null = null;
let streamInstanceId: string | null = null;

function connectSpaceStream(spaceId: string, apiKey: string) {
  const url = new URL(`wss://emit.run/api/v1/realtime/spaces/${spaceId}`);
  url.searchParams.set("token", apiKey);
  if (lastSeq !== null) url.searchParams.set("since", String(lastSeq));
  if (streamInstanceId) url.searchParams.set("streamInstanceId", streamInstanceId);

  const ws = new WebSocket(url.toString());
  ws.onmessage = (e) => {
    const event = JSON.parse(e.data);

    if (typeof event.seq === "number") {
      lastSeq = event.seq;
    }
    if (typeof event.streamInstanceId === "string") {
      streamInstanceId = event.streamInstanceId;
    }

    if (event.type === "connected" && event.missed) {
      revalidateDashboardState();
    }
  };

  return ws;
}

This keeps reconnects fast while preventing stale counters after network drops, background-tab throttling, or actor restarts.


Common patterns

Submit-and-watch

Create a job on your backend and immediately stream progress back to the user. The client can connect directly to the single-job stream without a token (anonymous progress scope):

// Backend: create job and return ID to client
app.post("/api/start-report", async (req, res) => {
  const job = await createJob({ name: "generate-report", payload: req.body });
  res.json({ jobId: job.id });
});

// Frontend: stream progress directly
const { jobId } = await startReport(params);
const ws = new WebSocket(`wss://emit.run/api/v1/realtime/${jobId}`);

ws.onmessage = (e) => {
  const event = JSON.parse(e.data);
  if (event.type === "init" && event.progress) {
    updateProgressBar(event.progress.percent);
  }
  if (event.type === "progress") updateProgressBar(event.data.percent);
};

Dashboard live feed

Use the space stream with a jobs:read token to power a real-time operations dashboard:

const ws = new WebSocket(
  `wss://emit.run/api/v1/realtime/spaces/${spaceId}?token=${dashboardKey}`
);

ws.onmessage = (e) => {
  const event = JSON.parse(e.data);
  updateJobRow(event.jobId, { status: event.status, type: event.type });
};

On this page