# FastAPI SSE relay for the Responses API

The Responses API streams typed server-sent events over HTTP (`stream=True`). Your
FastAPI route has to re-frame them as SSE for the browser. Verified against the
streaming guide on 2026-09-26.

## The loop that fails

1. `client.responses.create(model=..., input=..., stream=True)` returns an iterator
   of typed events, not SSE frames. Do not forward `event.json()` raw and hope the
   browser parses it.
2. Forward only what the UI needs. For text, that is the
   `response.output_text.delta` events: take `event.delta` and send one SSE frame
   per delta: `data: ` + json.dumps({"delta": event.delta}) + two newlines.
3. End the stream on the `response.completed` event, then close.

## FastAPI route shape

- Return `StreamingResponse(gen(), media_type="text/event-stream")`.
- Set `Cache-Control: no-cache` and `X-Accel-Buffering: no` on the response. Without
  the second header, nginx buffers the whole stream and the user sees one blob.
- `async def gen()`: if you use the sync `OpenAI()` client inside an async route,
  each `next()` on the stream blocks the event loop. Use `AsyncOpenAI()` in async
  routes, or run the sync iterator in a thread.

## Check before you ship

- curl the route with `-N` and confirm frames arrive incrementally, not at the end.
- Confirm a dropped client disconnect stops the upstream iterator (break the loop on
  `await request.is_disconnected()`).
- Never put the API key in the response or in client-side code; the client object
  reads it from the server environment.