Streaming
Server-Sent Events with real progress, draft previews, and the final result.
Design tasks take time — seconds for mechanical fills, longer for complex creative work. Instead of waiting on a single JSON response, add ?stream=true to any POST endpoint to receive a stream of Server-Sent Events (SSE): textual progress reports, rendered previews of the output before it's finalized, and the final result.
This is what powers engaging loading states: you can show the agent "thinking", surface draft previews as they render, and swap in the final design when it lands.
Event types
| Event | Payload | Meaning |
|---|---|---|
progress | { "message": string } | A human-readable progress update, safe to show directly to users. |
preview | { "id": string, "url": string, "label"?: string } | A rendered draft preview of one output frame, before it's finalized. |
completed | the full response object | The final result — identical to the non-streaming response. |
error | { "code": string, "message": string } | Terminal error. The stream closes after this event. |
Every stream ends with exactly one terminal event — completed or error — after which the connection closes.
Preview IDs. Each preview event carries an id identifying the frame it previews — for named outputs, it's the output's id. Track previews by id in a map: when a new preview arrives with the same id, it supersedes the earlier one. This is how you render live, updating previews of several frames in a multi-output request.
event: progress
data: {"message":"Reading brand kit rmd_cj4hbqz4f…"}
event: progress
data: {"message":"Drafting the Instagram story layout…"}
event: preview
data: {"id":"instagram_story_1","url":"https://cdn.rivermark.ai/rmd_cj4hbqz2w0003mz6x7p9q2xe/k3f8qz1w/preview-01.png","label":"Working draft"}
event: preview
data: {"id":"instagram_story_1","url":"https://cdn.rivermark.ai/rmd_cj4hbqz2w0003mz6x7p9q2xe/k3f8qz1w/preview-02.png","label":"After review fixes"}
event: completed
data: { "design_id": "rmd_cj4hbqz2w0003mz6x7p9q2xe", "output": [ … ] }Consuming streams
SSE is a simple text protocol — events arrive as event: and data: lines. Every language can consume it with a plain HTTP request and a line reader; no client library needed.
async function* streamDesign(body: object) {
const res = await fetch("https://api.rivermark.ai/design?stream=true", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RIVERMARK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
let event = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Keep an incomplete line buffered until the next network chunk.
let newline;
while ((newline = buffer.indexOf("\n")) !== -1) {
const line = buffer.slice(0, newline).replace(/\r$/, "");
buffer = buffer.slice(newline + 1);
if (line.startsWith("event: ")) event = line.slice(7);
else if (line.startsWith("data: ")) yield { type: event, data: JSON.parse(line.slice(6)) };
}
}
}
// usage: render live previews, keyed by id
const previews = new Map<string, string>();
for await (const { type, data } of streamDesign({
task: "Create an Instagram story for the summer sale.",
references: [{ type: "design", id: brandkitId }],
})) {
if (type === "progress") setStatus(data.message);
// "Drafting the Instagram story layout…"
else if (type === "preview") previews.set(data.id, data.url);
// same id supersedes the older preview
else if (type === "completed") render(data.output);
else if (type === "error") throw new Error(data.message);
}Notes
previewURLs are even shorter-lived than final render URLs — treat them as display-only.- Progress messages are written for end users, not just developers — showing them verbatim is a feature.
- Streaming works on
POST /designandPOST /brandkit. Uploads and other endpoints return normally. - If the client disconnects mid-stream, the request still runs to completion — the design appears in your API dashboard's activity log, and a listing endpoint for designs created in a time window is on the roadmap.

