tiangolo/fastapi · error · ValueError
SSE ' ' must be a single line
Error message
SSE '{field_name}' must be a single line What it means
Raised by `_check_single_line` (fastapi/sse.py:38) as a ValueError when an SSE field ('event', 'id', or other single-line field) contains a carriage return ('\r') or newline ('\n'). The SSE wire format uses newlines as field delimiters, so an embedded newline would corrupt the stream by splitting one event into multiple fields. The validator is attached via `AfterValidator` to the `event` and `id` fields of `ServerSentEvent`.
Solutions
- Sanitize the value: strip/replace line terminators before assignment, e.g. `event=event.replace('\r', '').replace('\n', ' ')`.
- Use multiple `ServerSentEvent` yields for logically separate lines instead of embedding newlines.
- Validate input upstream: reject or truncate strings containing '\n'/'\r' before constructing the event.
Example fix
// before
yield ServerSentEvent(event='update\nrestart', data=payload)
// after
event_name = 'update restart' if '\n' in raw else raw
yield ServerSentEvent(event=event_name.replace('\n', ' '), data=payload) Defensive patterns
Strategy: validation
Validate before calling
from fastapi.sse import ServerSentEvent
def safe_event(event: str | None = None, **kw) -> ServerSentEvent:
if event is not None:
event = event.replace('\r', '').replace('\n', ' ')
return ServerSentEvent(event=event, **kw) Type guard
def is_single_line(value: object) -> bool:
return isinstance(value, str) and '\r' not in value and '\n' not in value Prevention
- Sanitize any user-controlled string before assigning to event/id.
- Unit-test SSE event construction with inputs containing newlines.
- Treat event/id fields as opaque tokens, not free text.
When it happens
Trigger: Constructing `ServerSentEvent(event='update\nmore', data=...)` or `ServerSentEvent(id='abc\ndef')`. Yielding an event whose `event`/`id` was built from user input that contains a newline. Multi-line strings assigned to `event` or `id`.
Common situations: Passing untrusted/user-controlled strings into `event` or `id` without sanitization. Building event names from concatenated values that introduced a '\n'. Copying log lines (which contain newlines) into an SSE field.
Related errors
- Cannot set both 'data' and 'raw_data' on the same…
- SSE 'id' must not contain null characters
- Code block (lines - ) has different language than the…
- {"errors": exc.errors(), "body": body.decode()}
- {"errors": exc.errors(), "body": body.decode()}
AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11).
Data as JSON: /api/errors/aeae7ed34e07c0af.
Report an issue: GitHub.
Appendix: source
Thrown at fastapi/sse.py:38
class EventSourceResponse(StreamingResponse):
"""Streaming response with `text/event-stream` media type.
Use as `response_class=EventSourceResponse` on a *path operation* that uses `yield`
to enable Server Sent Events (SSE) responses.
Works with **any HTTP method** (`GET`, `POST`, etc.), which makes it compatible
with protocols like MCP that stream SSE over `POST`.
The actual encoding logic lives in the FastAPI routing layer. This class
serves mainly as a marker and sets the correct `Content-Type`.
"""
media_type = "text/event-stream"
def _check_single_line(v: str | None, field_name: str) -> str | None:
if v is not None and ("\r" in v or "\n" in v):
raise ValueError(f"SSE '{field_name}' must be a single line")
return v
def _check_event_single_line(v: str | None) -> str | None:
return _check_single_line(v, "event")
def _check_id_valid(v: str | None) -> str | None:
if v is not None and "\0" in v:
raise ValueError("SSE 'id' must not contain null characters")
return _check_single_line(v, "id")
class ServerSentEvent(BaseModel):
"""Represents a single Server-Sent Event.
When `yield`ed from a *path operation function* that uses
`response_class=EventSourceResponse`, each `ServerSentEvent` is encodedView on GitHub (pinned to 3e8d1526d8)