Streaming (Server-Sent Events)
In API 2.0, streaming is not an alternative to polling — it is how the AutoGenerator delivers results. Generate, edit, and download all return a stream, and the finished presentation's URL arrives on the stream's terminal event.
This page covers the stream itself: the event contract, how to consume it, reconnection, and limits. For the endpoints that mint a stream, see the AI Builder API.
In 1.0, streaming was optional — an alternative to polling
GET /api/v1/autogenerator/status, using POST /api/v1/streams/sessions
and a workflow.* / task.* event catalog. That contract is unchanged
and documented under version 1.0.
The v2 stream is a different, simpler contract: one event shape, and no status endpoint to fall back on.
How it works
1. POST /api/v2/generate → { resData: { callback_id } }
2. POST /api/v2/streams/sessions → { data: { stream_url } }
body: { callback_id }
3. open stream_url (SSE) → progress events…
terminal event + download_url
Step 2 is Bearer-authenticated through API Gateway. Step 3 is served by a
dedicated host (stream-api-<env>.myprezent.com) so the long-lived
connection is not subject to gateway timeouts.
POST /api/v2/autogenerator/download skips step 2 — it returns a
stream_url directly.
The token is embedded in stream_url, so you send no Authorization
header when opening it. Browsers' EventSource cannot set headers at
all, which is why the token travels in the URL.
The event contract
Every event — progress and terminal alike — has the same four-field shape, so a single parser handles the whole stream:
id: 7
event: status
data: {"status":"inprogress","message":"Analyzing the key elements","callback_id":"3f8e7d61-…"}
| Field | Type | Notes |
|---|---|---|
status | string | inprogress while working; then exactly one of success, failed, cancelled. |
message | string | Human-readable step description — written for display, safe to show a user verbatim. On a failed event, the reason. |
callback_id | string | The generation or edit turn this event belongs to. |
download_url | string | null | Only on a success event. The finished file. |
The SSE event: name is status for progress events and done,
error, or cancelled for the terminal one. Branch on the status
field, not the event name — it carries the same information with one
less special case.
The stream closes after the terminal event.
Progress messages
Progress messages come from a fixed set, in pipeline order:
| # | Message | When |
|---|---|---|
| 1 | Understanding your prompt | Always |
| 2 | Checking for app references | Only when the prompt references a connected app |
| 3 | Analyzing the key elements | Always |
| 4 | Adjusting structure for clarity | Always |
| 5 | Optimizing spacing and alignment | Always |
| 6 | Crafting your speaker notes | Only when speaker notes were requested |
| 7 | Finalizing your slides | Always |
Steps that do not apply to your request are skipped, and each message is sent at most once per turn. Do not treat this as a fixed-length progress bar — drive your UI off the messages themselves, or off an indeterminate spinner.
Connecting to an app may substitute a more specific message (for example
"Fetching data from Google Drive"), and app-related problems surface as
additional notice events that are still inprogress — they inform, they
do not end the stream.
A typical stream
event: status
data: {"status":"inprogress","message":"Understanding your prompt","callback_id":"3f8e…"}
event: status
data: {"status":"inprogress","message":"Analyzing the key elements","callback_id":"3f8e…"}
event: status
data: {"status":"inprogress","message":"Optimizing spacing and alignment","callback_id":"3f8e…"}
event: status
data: {"status":"inprogress","message":"Finalizing your slides","callback_id":"3f8e…"}
event: done
data: {"status":"success","message":"Your presentation is ready","callback_id":"3f8e…","download_url":null}
download_url can be null on a successOn a generation stream, a null download_url means the deck was
built but not yet exported to a file. That is a success, not a failure —
call
POST /api/v2/autogenerator/download
to produce the file, then read that stream.
On a download stream, download_url is always populated.
Consuming the stream
Open stream_url with any standard SSE client. Browsers have
EventSource
built in; server-side, use an SSE library or parse the frames yourself.
- JavaScript
- Python
- curl
// Node: npm install eventsource • Browser: EventSource is built in
import { EventSource } from 'eventsource';
const BASE = 'https://api.prezent.ai';
const HEADERS = {
Authorization: `Bearer ${PREZENT_API_KEY}`,
'Content-Type': 'application/json',
};
// 1. Start the generation.
const gen = await fetch(`${BASE}/api/v2/generate`, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({ prompt: 'Five-slide pitch on solar adoption' }),
}).then((r) => r.json());
const callbackId = gen.resData.callback_id;
// 2. Mint a stream URL.
const session = await fetch(`${BASE}/api/v2/streams/sessions`, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({ callback_id: callbackId }),
}).then((r) => r.json());
// 3. Read it. No Authorization header — the token is in the URL.
const downloadUrl = await new Promise((resolve, reject) => {
const es = new EventSource(session.data.stream_url);
es.onmessage = (e) => {
const event = JSON.parse(e.data);
if (event.status === 'inprogress') {
// Safe to show the user directly.
console.log(event.message);
return;
}
es.close();
if (event.status === 'success') resolve(event.download_url ?? null);
else reject(new Error(`${event.status}: ${event.message}`));
};
es.onerror = () => { es.close(); reject(new Error('stream failed')); };
});
console.log('done:', downloadUrl);
onmessage receives every event regardless of its SSE event: name, so
one handler covers progress and terminal events alike.
import json
import httpx # pip install httpx httpx-sse
from httpx_sse import connect_sse
BASE = "https://api.prezent.ai"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
with httpx.Client(base_url=BASE, headers=HEADERS, timeout=30) as c:
# 1. Start the generation.
gen = c.post("/api/v2/generate",
json={"prompt": "Five-slide pitch on solar adoption"})
gen.raise_for_status()
callback_id = gen.json()["resData"]["callback_id"]
# 2. Mint a stream URL.
sess = c.post("/api/v2/streams/sessions", json={"callback_id": callback_id})
sess.raise_for_status()
stream_url = sess.json()["data"]["stream_url"]
# 3. Read it. No auth header, and no read timeout on a long-lived stream.
download_url = None
with httpx.Client(timeout=None) as sc:
with connect_sse(sc, "GET", stream_url) as events:
for sse in events.iter_sse():
event = json.loads(sse.data)
if event["status"] == "inprogress":
print(event["message"]) # safe to show a user
continue
if event["status"] != "success":
raise RuntimeError(f"{event['status']}: {event['message']}")
download_url = event.get("download_url")
break
print("done:", download_url)
BASE=https://api.prezent.ai
# 1. Start the generation.
GEN=$(curl -s -X POST "$BASE/api/v2/generate" \
-H "Authorization: Bearer $PREZENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Five-slide pitch on solar adoption"}')
CALLBACK=$(echo "$GEN" | jq -r '.resData.callback_id')
# 2. Mint a stream URL.
SESSION=$(curl -s -X POST "$BASE/api/v2/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. Read it. -N disables output buffering; the token is in the URL.
curl -N "$URL"
Reconnecting after a drop
Each event carries a monotonic id:. If the connection drops, reconnect
with ?last_event_id=N — where N is the last id: you processed — and
the server replays everything after N, then resumes live:
const es = new EventSource(`${streamUrl}&last_event_id=${lastSeenId}`);
Most browser EventSource implementations send a Last-Event-ID header
automatically on reconnect; that is accepted too.
If the token has expired by the time you reconnect, mint a fresh URL with
POST /api/v2/streams/sessions — the new stream replays from the start
of the turn, so a late reconnect without a stored last_event_id
still loses nothing.
For an edit, a freshly minted URL automatically replays from the point the edit was queued, so you see the whole edit turn even if you connect a moment late.
Keepalives
The server emits a :ping comment every 15 seconds. Standard SSE
clients ignore comment lines — they exist to keep idle connections alive
through load balancers and proxies.
Set no read timeout on the connection. A generation is legitimately quiet for stretches, and the pings are what distinguish "still working" from "dead". Both client examples above set the timeout to none for exactly this reason.
Token lifecycle
The token in stream_url is signed and bound to your API key and the
presentation. It expires about 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/v2/streams/sessionsagain for the samecallback_id— a fresh URL with a new window, replaying from the start of the turn.
Once open, a connection is not cut short by the token expiring — the 5-minute window governs how long the URL can be used to open a stream.
Tokens are deliberately short-lived so a leaked URL is useless quickly.
Limits
| Stream URL scope | One URL is bound to one callback_id. Stream another presentation with another session. |
| Token TTL | ~5 minutes. Call POST /api/v2/streams/sessions again to refresh. |
| Event replay window | ~2 hours from the start of the turn. Reconnects with ?last_event_id= must fall inside it. |
callback_id lifetime | ~3 hours, after which no new stream can be minted for it. |
| Rate limits | The streaming connection itself is not rate-limited per second; the session-create endpoint shares the standard per-key limits. |
| Concurrency | One turn runs at a time per presentation — an edit is rejected while a previous turn is still running. |
See also
- AI Builder API — the endpoints that mint a stream, and a full end-to-end example.
- Webhooks — push completion into your own backend instead of holding a connection open.
- API Reference (interactive) — the full OpenAPI spec.