RivermarkRivermark
Core concepts

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

EventPayloadMeaning
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.
completedthe full response objectThe 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.

A typical stream
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.

stream.ts
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

  • preview URLs 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 /design and POST /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.

On this page