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

# Queue Events (SSE)

> Stream video processing job updates over Server-Sent Events

Open a persistent connection and job lifecycle events arrive as they happen. Use it for live progress in your UI instead of polling.

```javascript theme={null}
// Cloud authenticates this stream, so proxy it from your server: EventSource
// cannot send an Authorization header, and a key does not belong in a bundle.
const source = new EventSource("/api/openinary/queue/events");

source.addEventListener("job:progress", (e) => {
  const { jobId, progress } = JSON.parse(e.data);
  updateProgressBar(jobId, progress);
});

source.addEventListener("job:completed", (e) => {
  const { jobId } = JSON.parse(e.data);
  markDone(jobId);
});

source.addEventListener("job:error", (e) => {
  const { jobId, error } = JSON.parse(e.data);
  console.error(`Job ${jobId} failed:`, error);
});
```

```
GET /queue/events
```

**Auth:** API key on Cloud, public and rate limited self-hosted.

Read the raw stream with `curl -N https://cdn.openinary.dev/queue/events -H "Authorization: Bearer <api_key>"`, or without the header on your own instance.

## Events

Each event follows the SSE format: `event: <type>\ndata: <json>\n\n`

| Event | Fires when | `data` |
| - | - | - |
| `connected` | The connection opens | `{"clientId": "client_abc123"}` |
| `job:created` | A job is added to the queue | `{"jobId": "job_xyz", "filePath": "videos/clip.mp4", "status": "pending", "priority": 2}` |
| `job:started` | A job begins processing | `{"jobId": "job_xyz", "filePath": "videos/clip.mp4", "status": "processing"}` |
| `job:progress` | Progress advances, 0 to 100 | `{"jobId": "job_xyz", "progress": 47}` |
| `job:completed` | A job finishes successfully | `{"jobId": "job_xyz", "filePath": "videos/clip.mp4", "status": "completed"}` |
| `job:error` | A job fails | `{"jobId": "job_xyz", "filePath": "videos/clip.mp4", "status": "error", "error": "FFmpeg process exited with code 1"}` |
| `heartbeat` | Periodically, to keep the connection alive | `{"timestamp": 1710498600000}` |

The heartbeat `timestamp` is Unix epoch milliseconds (`Date.now()`), not an ISO string.

The `priority` on `job:created` is always `2`. The stream builds its payload from a converted job object that drops the stored priority, so a thumbnail queued at `1` still reports `2` here. Read [GET /queue/jobs](/api-reference/queue/jobs) for the real value.

## Response headers

| Header | Value |
| - | - |
| `Content-Type` | `text/event-stream` |
| `Cache-Control` | `no-cache` |
| `Connection` | `keep-alive` |
| `X-Accel-Buffering` | `no` |

## Related

<CardGroup cols={2}>
  <Card title="Queue Jobs" icon="list" href="/api-reference/queue/jobs">
    Query job details or manage individual jobs.
  </Card>

  <Card title="Video Status" icon="video" href="/api-reference/video/status">
    Check processing status by file path.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.