Streaming (Server-Sent Events)
Most Prezent operations are asynchronous — you POST /api/v1/autogenerations,
receive a callback_id, and then check progress. You have three ways to
do that:
| Mechanism | Best for |
|---|---|
Polling (GET /status) | Low-volume, simple scripts. |
| Webhooks | Push notifications into your backend at completion. |
| Streaming (SSE) ← you are here | Real-time progress updates inside an app, agent, or UI — every milestone as it happens. |
Streaming complements the other two. Subscribe to the stream while a
job is running and you'll see workflow.started, every task.completed,
and finally workflow.completed or workflow.failed, with each event
arriving within milliseconds of when the pipeline reaches it.
How it works in one diagram
1. POST /api/v1/autogenerations → { callback_id } (existing)
2. POST /api/v1/streams/sessions → { stream_url, expires_at }
body: { callback_id }
3. new EventSource(stream_url) → live event stream
Step 2 is bearer-authenticated and runs through API Gateway; step 3 is served by a dedicated public host so the long-lived connection is unaffected by gateway timeouts.
Endpoint
POST /api/v1/streams/sessions
Creates a short-lived streaming URL for a callback_id that the same
API key already owns. The URL is single-use-friendly but not single-use
— it expires after 5 minutes (call this endpoint again to refresh).
Request body
{
"callback_id": "cb_01HZQ3Y7K3D6V0A0CS7HD3"
}
Response (201 Created)
{
"success": true,
"data": {
"session_id": "cb_01HZQ3Y7K3D6V0A0CS7HD3",
"stream_url": "https://stream-api.myprezent.com/v1/streams/cb_01HZQ3Y7K3D6V0A0CS7HD3?token=eyJhbGciOiJIUzI1NiIs...",
"expires_at": "2026-06-09T10:05:00Z"
}
}
| Field | Type | Description |
|---|---|---|
session_id | string | Same as the callback_id you passed in. |
stream_url | string | Full URL to open with EventSource (or fetch + ReadableStream). |
expires_at | string (ISO-8601 UTC) | When the embedded token stops being accepted. Re-call POST /streams/sessions to mint a new URL. |
Errors
| Code | Status | Meaning |
|---|---|---|
MISSING_REQUIRED_FIELD | 400 | Body did not include callback_id. |
STREAM_CALLBACK_NOT_FOUND | 404 | No job with that callback_id was found, or it is not owned by the calling API key. |
Consuming the stream
Open the stream_url with any standard SSE client. Browsers have
EventSource
built in; server-side languages use fetch + ReadableStream or an
SSE library.
- JavaScript (browser)
- Python (httpx)
- curl
// 1. Kick off the job (existing API).
const { data: job } = await fetch('https://api.myprezent.com/api/v1/autogenerations', {
method: 'POST',
headers: {
Authorization: `Bearer ${PREZENT_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ prompt: 'Five-slide pitch on solar adoption' }),
}).then(r => r.json());
// 2. Ask for a streaming URL.
const { data: session } = await fetch('https://api.myprezent.com/api/v1/streams/sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${PREZENT_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ callback_id: job.callback_id }),
}).then(r => r.json());
// 3. Open the stream.
const es = new EventSource(session.stream_url);
es.addEventListener('workflow.started', e => {
console.log('started', JSON.parse(e.data));
});
es.addEventListener('task.completed', e => {
const evt = JSON.parse(e.data);
console.log(`task ${evt.data.task_name} done`);
});
es.addEventListener('workflow.completed', e => {
console.log('done', JSON.parse(e.data));
es.close();
});
es.addEventListener('workflow.failed', e => {
console.error('failed', JSON.parse(e.data));
es.close();
});
es.onerror = (err) => {
// Connection dropped. If you've stored the last `id:` you saw, you
// can refresh the URL and reconnect with ?last_event_id=N to resume
// without losing events.
console.warn('stream error', err);
};
import json
import httpx
API_KEY = "..."
BASE = "https://api.myprezent.com"
with httpx.Client() as c:
# 1. Kick off the job.
job = c.post(f"{BASE}/api/v1/autogenerations",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"prompt": "Five-slide pitch on solar adoption"}).json()["data"]
# 2. Get a stream URL.
session = c.post(f"{BASE}/api/v1/streams/sessions",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"callback_id": job["callback_id"]}).json()["data"]
# 3. Stream events.
with httpx.stream("GET", session["stream_url"], timeout=None) as r:
last_id = None
event_type, data_lines = None, []
for line in r.iter_lines():
if line.startswith("id: "):
last_id = int(line[4:])
elif line.startswith("event: "):
event_type = line[7:]
elif line.startswith("data: "):
data_lines.append(line[6:])
elif line == "": # frame boundary
if event_type and data_lines:
payload = json.loads("\n".join(data_lines))
print(f"#{last_id} {event_type}: {payload['data']}")
if event_type in ("workflow.completed", "workflow.failed"):
break
event_type, data_lines = None, []
# 1. Job
JOB=$(curl -s -X POST https://api.myprezent.com/api/v1/autogenerations \
-H "Authorization: Bearer $PREZENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Five-slide pitch on solar adoption"}')
CALLBACK=$(echo "$JOB" | jq -r '.data.callback_id')
# 2. Stream URL
SESSION=$(curl -s -X POST https://api.myprezent.com/api/v1/streams/sessions \
-H "Authorization: Bearer $PREZENT_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"callback_id\": \"$CALLBACK\"}")
URL=$(echo "$SESSION" | jq -r '.data.stream_url')
# 3. Stream — `curl -N` disables output buffering.
curl -N "$URL"
Event catalog
Every frame on the wire is shaped like:
id: <integer monotonic per callback_id>
event: <event name>
data: { "type": "<event name>", "callback_id": "...", "ts": "...", "data": {...}, "seq": <integer> }
event name | When | data payload includes |
|---|---|---|
workflow.started | Job has been picked up and started executing. | workflow_name, started_at |
task.completed | A pipeline subtask finished successfully. | task_name, workflow_name, completed_at |
workflow.completed | The entire job finished successfully. Stream closes after this event. | workflow_name, completed_at, outputs_summary |
workflow.failed | The job failed. Stream closes after this event. | workflow_name, failed_at, error_log_preview |
A typical stream looks like:
event: workflow.started
data: {"type":"workflow.started","callback_id":"cb_...","seq":1,...}
event: task.completed
data: {"type":"task.completed","callback_id":"cb_...","seq":2,...}
event: task.completed
data: {"type":"task.completed","callback_id":"cb_...","seq":3,...}
event: workflow.completed
data: {"type":"workflow.completed","callback_id":"cb_...","seq":4,...}
Reconnecting after a drop
If your connection drops mid-stream, refresh the URL and reconnect with
?last_event_id=N (where N is the id: of the last event you
processed). The server will replay every event after N from a
30-minute window, then resume the live stream.
const es = new EventSource(`${session.stream_url}&last_event_id=${lastSeenId}`);
Most browser EventSource implementations send a Last-Event-ID
header automatically on reconnect; we accept that too.
Keepalives
The server emits a :ping comment every 15 seconds. Standard SSE
clients ignore comment lines; they're there to keep idle TCP
connections alive through load balancers and proxies (we run a
4000-second idle timeout on our side).
Token lifecycle
The token in the stream_url is signed with HS256 and bound to your
API key + the callback_id. It expires 5 minutes after issue. You can:
- Reuse the same URL within those 5 minutes for any number of reconnect attempts.
- Refresh by calling
POST /api/v1/streams/sessionsagain for the samecallback_id— you get a fresh URL with a new 5-minute window.
Tokens are intentionally short-lived so a leaked URL is useless after a coffee break.
Limits
- One stream URL is bound to one
callback_id. To stream another job, open another session. - 5-minute token TTL — call
POST /streams/sessionsagain to refresh. - 30-minute event replay window — reconnects with
?last_event_id=must happen within 30 minutes of the job starting. - No per-second rate limit on the streaming connection itself; the session-create endpoint shares the standard per-API-key rate limits.
See also
- Webhooks — for push notifications into your own backend.
- Auto Generate — to start a streaming-eligible job.
- API Reference (interactive) — full OpenAPI spec.