SSE for API Routes
Updated 10 minutes ago • May 22, 2026
Use this page when you expose a chat endpoint and want live chunk streaming in the browser.
Recommended server pattern (toSSE)
Good to know:
toSSE(...)is the route-level helper you usually want. It sets the SSE headers and writes the stream in SSE format. Authenticate and rate-limit this route before callingagent.streamChat(...); do not trust client-sent context for authorization.
Recommended client pattern for React
If you are building a React client, start with @kortyx/react.
Use chat.messages for finalized history and chat.streamContentPieces for the current in-flight assistant response.
Good to know:
useChat(...)already separates assistant text, structured streams, interrupts, and storage. Most React apps should start here instead of manually reducing raw chunks.
Custom React UI with structured streams
If you want your own UI but not the full chat abstraction, use useStructuredStreams() and only wire the parts you care about.
This keeps the structured-stream reducer logic framework-owned while leaving text rendering and layout up to you.
Example: render a streamed email draft
If a node uses useReason({ structured: { fields: { body: "text-delta", bullets: "append" } } }), the client can render the draft while the model is still writing:
kind semantics
Structured chunks use one of four kinds:
set: replace a value at a pathappend: append items to an array at a pathtext-delta: append text to a string at a pathfinal: replace the whole object and mark the stream complete
You usually do not need to implement these rules yourself. useStructuredStreams() and useChat() already apply them for you.
Path behavior
pathis a dot-separated location inside the object being built- raw structured chunks, manual
useStructuredData(...)calls, anduseReason({ structured: { fields } })can use dotted paths such asdraft.body - numeric path segments target array indexes, such as
sections.0.body useReason({ structured: { fields } })can use*as a single-segment wildcard, such asassessment_points.*.criteria_label; emitted chunks always contain concrete pathsapplyStructuredChunk(...)throws on malformed paths, impossible container-shape conflicts,appendon non-arrays,text-deltaon non-strings, and any chunk that arrives afterfinalfinalreplaces the whole accumulated object and should be treated as the source of truth
Good to know: If a single object streams multiple fields over time, keep one
streamIdfor that whole object. Use differentstreamIdvalues only for independent objects.
Good to know:
streamIdis the update identity for structured streams. If a node emits multiple relateduseStructuredData(...)calls, keep the samestreamIdso the client updates one object instead of creating many.
Advanced: manual reducer path
If you are not using React, or you want the raw reducer directly, use applyStructuredChunk(...) from kortyx/browser.
consumeStream(...)
If you prefer the callback helper:
Infrastructure notes
toSSE(...)andcreateStreamResponse(...)setcontent-type: text/event-stream- they also set
cache-control: no-cache,connection: keep-alive, andx-accel-buffering: no - keep this route on a runtime that supports streaming responses
- if you run behind a proxy or CDN, make sure response buffering is disabled
Good to know: If your client needs a single buffered result instead of live chunks, expose a non-stream mode and return
collectBufferedStream(...)from your route.
Low-level helper
Use createStreamResponse(...) only when you already have your own AsyncIterable<StreamChunk> and want to convert it to SSE directly.
For chunk types and protocol details, see Stream Protocol.