> ## Documentation Index
> Fetch the complete documentation index at: https://docs.motioness.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SSE streaming

Subscribe to live asset events via Server-Sent Events.

# SSE streaming

Motioness emits a typed event stream for every asset. Subscribe with `EventSource` to drive progress UI, tail logs, or wire up CI builds.

## Three SSE endpoints

| Endpoint | Auth | Use |
|---|---|---|
| `GET /v1/{projectKey}/events/{assetId}` | Public (project key) | Browser / CLI / build script |
| `GET /api/assets/{id}/events` | Session cookie | Dashboard, per-asset detail |
| `GET /api/projects/{id}/activity` | Session cookie | Dashboard, project-wide activity feed |

## Subscribe

**Browser**

```ts
    const es = new EventSource(
      `https://motioness.com/v1/${PK}/events/${ASSET_ID}`,
    );

    es.addEventListener('stage_completed', (e) => {
      const data = JSON.parse(e.data);
      console.log(`stage ${data.stage} done in ${data.latency_ms}ms`);
    });

    es.addEventListener('ready', () => {
      console.log('mp4 hot');
      es.close();
    });

    es.addEventListener('failed', (e) => {
      console.error(JSON.parse(e.data));
      es.close();
    });
    ```

    `EventSource` automatically reconnects and resumes from `Last-Event-ID`.

  **curl**

```bash
    curl -N "https://motioness.com/v1/$KEY/events/$ASSET_ID"
    ```

    Each event arrives as three lines:

    ```text
    id: 1714801234567
    event: stage_completed
    data: {"stage":"stage1","latency_ms":4820,"asset_id":"a25b…","ts":1714801234567}

    ```

  **Node**

```ts
    import { EventSource } from 'eventsource';

    const es = new EventSource(
      `https://motioness.com/v1/${PK}/events/${ASSET_ID}`,
    );

    es.onmessage = (e) => console.log(e.data);
    es.onerror = (err) => console.error(err);
    ```

## Event types

The closed enum mirrors the [asset lifecycle](/concepts/asset-lifecycle):

```text
queued
vision_started, vision_completed, vision_failed
stage_started, stage_completed, stage_failed
retry_scheduled
r2_write_completed
webhook_dispatched, webhook_delivered, webhook_failed, webhook_dead_letter
ready
failed
cancelled
```

Each event includes `ts`, `attempt`, `stage` (when applicable), `latency_ms` (on `*_completed`), `error_code` + `error_msg` (on `*_failed`), `request_id`.

## Reconnect with Last-Event-ID

The server emits `id: <ms>` on every event. On reconnect, browsers automatically include `Last-Event-ID: <last-ms>`; the server replays events newer than the cursor.

For non-browser clients you have to set the header yourself:

```ts
const headers = lastSeenId ? { 'Last-Event-ID': String(lastSeenId) } : {};
const res = await fetch(url, { headers });
```

## Connection cap

Server caps connections at **5 minutes**. Most generations finish in under 60 seconds, so the cap is a safety net. Reconnect with the cursor to continue.

## Heartbeats

Server sends `: ping` (SSE comment) every 15 seconds to keep proxies alive. Standard `EventSource` ignores them silently.

## Auth-gated streams

For dashboard use, send the session cookie:

```bash
curl -N -b "better-auth.session_token=…" \
  "https://motioness.com/api/projects/$PROJECT_ID/activity"
```

The auth-gated streams emit the same event shape across all assets in the project — useful for activity feeds.

## Webhooks vs SSE

| | Webhooks | SSE |
|---|---|---|
| Use | Server-side, fire-and-forget | Browser, real-time UI |
| Persistence | Durable retries (5 attempts) | Best-effort |
| Auth | Outbound (your endpoint receives) | Inbound (you connect) |
| Latency | ~100ms after event | ~10ms after event |

For dashboards, prefer SSE. For server-side reactions (re-encoding, indexing, etc.), prefer webhooks — they survive your service being briefly down.