Skip to main content

SSE wire contract

The AI chat endpoints stream Server-Sent Events (text/event-stream). This page is the event reference; the source of truth is the parser + types in @kaleidr/inference-ui (packages/inference-ui/src/control/sse.ts and types.ts). If you use kaleidr.js, the chat bundle parses this for you — you only need this page to build a custom client.

Events

event:Payload (shape)When
start / phase{ type, phase, session_id }Lifecycle — thinking / waiting phases.
tokenunnamed eventdata: { "text": "…" }Prose, streamed incrementally. May contain [[Place]] markers.
place{ name, coordinates: { lat, lng }, address?, category?, source?, … }A resolved place (merge-by-name → a map pin). source: "vendor" marks a place from the organization's own uploaded data rather than one geocoded from the answer; style those pins differently if you wish, or ignore the field.
place_linkedpartial place fieldsMetadata enrichment of an already-emitted place.
early_actions{ actions: [ { type, payload } ] }Map actions (fitBounds / route / highlight) before the full reply.
quota{ used, limit, tier, reset_at }Mid-stream meter — surface remaining budget.
grounding{ sources: [ { title, url, tier, layer } ] }Sources for a grounded answer (grounded turns only). layer is "org" for a source from your organization's own uploaded data and "baseline" for everything else — present on every source, so you can render one rule rather than branching on whether the field is there. Label them distinctly when an answer mixes both: where your data and a third-party source disagree, Kaleidr surfaces the conflict rather than silently picking one, and a reader can only act on that if they can see which is which.
route{ type: "route", profile, origin, destination, primary: { geometry, distance_m, duration_s, … }, alternatives: [] }A journey-phrased turn ("route from A to B") — the engine-computed route, emitted before the prose so the map can draw while the answer streams.
prompt_options{ question, options: [ { label, send_text } ] }The assistant needs a disambiguation ("which Springfield?") — render as tappable chips and submit an option's send_text as the next user message.
end{ full_text, places: [], actions: [], … }Terminal envelope — the complete result.
error{ message }Terminal error.

A keep-alive comment line (: keep-alive) arrives every ~10s to hold the connection open through proxies — ignore it.

Tokens and structured data

token events stream plain prose. They may carry inline [[Place]] markers where a place is referenced (e.g. …cafes near [[Louvre]]…) — strip them for clean display, or ignore the prose entirely and drive your UI from the structured events.

Structured data arrives as events, never inline in the prose. Resolved places stream on place (and place_linked for late metadata) as they geocode; map actions arrive on early_actions; routes arrive on route; the authoritative final payload is the end event ({ full_text, places, actions }). A typical client renders pins from place events as they arrive and treats end as the source of truth.

Minimal client

const res = await fetch(`${API}/inference-api/b2b/v1/chat/control/stream`, {
method: 'POST',
headers: { 'X-Api-Key': key, 'Content-Type': 'application/json', Accept: 'text/event-stream' },
body: JSON.stringify({ messages: [{ role: 'user', content: 'cafes near the Louvre' }] }),
});
const reader = res.body.getReader();
// …decode chunks, split on \n\n, parse `event:` + `data:` lines…

See Endpoints for the request bodies, and Errors for failure handling.