reactor CLI for everyday use. Call the HTTP endpoint directly to
build your own log viewer or integrate outside the CLI.
Stream logs with the CLI
reactor logs streams your project’s model, resolved from reactor.yaml. Pass
-f/--follow to keep tailing live.
The rest of this page documents the GET /logs endpoint reactor logs calls. Reactor streams the
same logs as a live Server-Sent Events (SSE) stream. Open GET /logs with your API key as a bearer
token (Authorization: Bearer <key>) and read the stream.
The same key you use for the rest of the Reactor API is the credential the log stream accepts. The
endpoint is served under the same https://api.reactor.inc origin as the rest of the Reactor API.
The transport is plain HTTP and SSE. No SDK is required.
What you can stream
Use model logs to watch the whole deployment. Use session logs to debug one request. Use instance
logs to check one pod.
The
instance_id value is the identifier Reactor returns for the pod a session is currently bound
to. You can read it from GET /sessions/{session_id}/runtime once the session has been scheduled,
or list the live instances for a model with GET /models/{model_id}/instances.Open the stream
A call names exactly one ofmodel, session_id, or instance_id as a query parameter. Send your
API key as the bearer token in the Authorization header. A browser client sends its Clerk JWT in
the same header. The legacy Reactor-API-Key: <key> header is still accepted when no
Authorization header is present. Query-string credentials are never accepted (see
error responses).
200 OK with Content-Type: text/event-stream opens the stream. Anything else terminates the
request before the stream begins.
Error responses
The error body is JSON:
{"error": "<code>", "message": "<human detail>"}.
Event types
The stream uses a small, stable set of SSE event names. Every event carries a singledata: line
containing JSON.
event: log
A single log line emitted by the runtime.
line is the raw log line as emitted by the runtime. Reactor’s first-party models emit structured
JSON, so you can JSON.parse(line) to get per-line fields. Models that emit free-form text are
passed through verbatim.
stream_id is an opaque per-stream identifier. On a merged model= stream that tails many pods at
once, key a resume or dedupe cursor on stream_id, not on timestamp alone.
attributes is an optional object of platform-owned fields, kept separate from the untrusted
line. It currently carries attributes.instance_id — the pod that emitted the line — so a merged
model= stream can attribute each line to its source instance. The object is omitted when no such
field applies.
Every record the runtime writes carries the id of the live session, so an instance stream tells you
which session produced a line. Each record also names the phase it was written in, as state and
its coarser form runtime_state. A model that logs through get_logger() from reactor_runtime,
or through the standard library’s logging, gets the same fields.
event: heartbeat
An empty keepalive emitted on a regular cadence (every ~15 s by default) so proxies and load
balancers don’t close idle connections.
event: error
A server-side problem that is about to terminate the stream. The message is safe to render to end
users; debug_id correlates with Reactor’s internal log entry so support can investigate without
you leaking sensitive details.
error event is always followed by a terminal end event, so clients that only switch on
end still close cleanly.
event: end
The terminal frame. Every stream ends with exactly one end event, after which the server closes
the HTTP connection. The reason field tells you which boundary fired.
Limits and lifecycle
Streams are deliberately short-lived. Plan to reconnect periodically; the protocol is designed for it.
These values are the platform defaults today; they may be tuned per environment or per account. The
active limits for your stream are enforced server-side, so your client does not need to mirror them.
Streaming to a browser
Log streaming is a server-side operation, for two reasons:- A raw API key is long-lived and gives access to your whole account. Never send it to a browser.
- Session logs are the model runtime’s own container output. Reactor passes them through unchanged. There is no field allowlist and no redaction. The caller also picks the verbosity, so a stream can carry debug and trace lines. Treat the stream as internal detail. Filter it before your end users see it.
1
Keep the key in your backend
Store the API key in your server environment. Your frontend never sees it.
2
Open the upstream stream
From your backend, call
GET /logs with Authorization: Bearer <key>, exactly as shown above.3
Re-emit to your frontend
Forward the SSE frames to your own endpoint. Protect that endpoint with your own user session.
Filter or redact anything you do not want end users to read.
Reactor’s own dashboard streams logs directly from the browser with a Clerk session. That path is
first-party only, so partner applications cannot use it.
Putting it together
The expected client loop is short:1
Pick what to stream
Pick a
model (all instances, <slug>/<name>), a session_id, or an instance_id (one
instance). For an instance_id, fetch it from GET /sessions/{session_id}/runtime or
GET /models/{model_id}/instances.2
Open the stream
GET /logs?{model | session_id | instance_id}=… with your API key as the bearer token in the
Authorization header. There is no separate ticket step.3
Process events
Switch on the SSE event name. Handle
log, observe heartbeat, treat error as a soft warning,
and close cleanly on end.4
Reconnect when needed
On
end with reason: "deadline" (or shutdown/rate_limit), wait a short backoff and
reopen the stream with the same API key.Next
Authenticate the CLI
How to obtain and configure the credentials the Reactor API expects.
Deploy a release
Once a model is live, its sessions and instances are ready to stream from.