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
tsconst 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.
bashcurl -N "https://motioness.com/v1/$KEY/events/$ASSET_ID"
Each event arrives as three lines:
textid: 1714801234567event: stage_completeddata: {"stage":"stage1","latency_ms":4820,"asset_id":"a25b…","ts":1714801234567}
tsimport { 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:
textqueuedvision_started, vision_completed, vision_failedstage_started, stage_completed, stage_failedretry_scheduledr2_write_completedwebhook_dispatched, webhook_delivered, webhook_failed, webhook_dead_letterreadyfailedcancelled
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:
tsconst 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:
bashcurl -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.