Skip to main content
reactor::Reactor is one session, and the tracks and commands on it. It speaks raw JSON over the wire: open a session, send commands by name, receive generic message events. It works against any model — for the commands and events a specific one accepts, see the Model API Reference, or ask the running model itself with request_schema().
Async calls return std::future<T>, and failures arrive as exceptions from .get() — the same typed hierarchy a synchronous call throws. See ReactorError.
Movable, not copyable — a session has one owner. Destroying a connected client releases the native handle, but a creator that goes away without disconnect() leaves the session orphaned, and the next run cannot start until it clears.

Constructors

Signature
std::string
required
The model to connect to, as owner/name. A bare name resolves under reactor/.
ApiKey
An API key, exchanged for a session-scoped token when connecting. Scoped to this model, so a leak is worth a handful of sessions rather than everything the key can reach.
Jwt
A token minted elsewhere, used as it is. For a server that already holds one, or a client handed one by a backend that owns the key.
Options
Everything that is not the model or the credential — see below.
ApiKey and Jwt are distinct one-field structs rather than two std::string parameters, so the credential you meant is the credential that is used:
Example
Never ship an API key to an end-user’s machine. Mint a short-lived JWT on your server and pass it as Jwt. See Authentication.

Options

Definition
std::string
default:"https://api.reactor.inc"
The coordinator. reactor::LOCAL_API_URL is http://localhost:8080, for a local runtime.
bool
default:"false"
Accept a dev coordinator’s self-signed certificate and speak its local-development protocol. Pair it with api_url = reactor::LOCAL_API_URL.
Executor
Where control-event handlers run. Empty — the default — means the SDK’s own dispatcher thread.

Executor

Signature
By default the SDK runs control-event handlers on one thread of its own, serialised, never on a library thread. Give it an executor and it hands that callable each event instead — for a host with a loop of its own (Qt, ASIO, a game loop) that would rather own when handlers run.
Example
It is called from a library thread, so it must be safe to call from any thread.
Futures do not go through the executor. A promise is settled on the library’s own completion thread, so connect().get() on the same thread that would have run the executor cannot deadlock against it.
Media handlers do not go through it either. on_frame() and on_audio() run inline on the library’s delivery thread, on purpose — blocking in one is the backpressure. See VideoFrame.

Connecting

connect()

Creates — or adopts — a session and brings up the transport. Resolves when the session is Ready.
Signature
Definition
std::optional<std::string>
Join a session that already exists rather than creating one. This is how a second client attaches to the same session. A session adopted this way is not ended by disconnect() — it keeps running for its owner.
std::optional<std::uint32_t>
Adopt a connection slot a backend already registered for this session. The connection-level analogue of session_id; most callers building one connection per session leave it unset. See Multiple connections per session.
Throws the typed failure: UnauthorizedError for a token problem, ConflictError for a session a previous run left orphaned.
Example

reconnect()

Cycles the connection without ending the session — after a transient failure, or deliberately from Ready.
Signature
It tears the live connection down itself, so there is no need to disconnect() first — and doing so would end the very session this is about to reuse. Throws when there is no session to reconnect to.
RecvOnly tracks resume automatically. SendOnly tracks do not: a track published before the reconnect is not published after it, so publish again for anything you were sending. Track::published() says which side of that you are on, and push_frame() throws rather than pushing into a slot with nothing behind it.

disconnect()

Ends the session server-side and tears down the transport.
Signature
Not recoverable — there is no parameter that keeps the session alive instead. To disconnect and later resume the same session, call reconnect(). Only ends sessions this client created; one adopted via session_id is left running for its owner.

status()

Signature
Where the session is now, as a Status. Readable before connect() — a client that never connected reports Disconnected rather than nothing.

session_id()

Signature
The session’s id, once there is a session.

Commands and uploads

send_command()

Sends a command to the model and waits for its correlated reply.
Signature
std::string
required
The command name. Must match a command the model defines.
Json
The payload. reactor::Json is an alias for nlohmann::json.
std::map<std::string, FileRef>
Files to pass as named parameters — see upload_file().
The reply is {type, data}, or empty when the handler ran and acknowledged the command without returning a message, as an auto-generated set_<field> setter does. Empty is not a failure and is not folded into one:
Example
To fire a command without waiting on the reply, keep the future and call .get() later — or drop it, which sends the command all the same. These futures are settled by a promise rather than by std::async, so dropping one neither blocks nor cancels; it only means a failure has nowhere to be thrown.

upload_file()

Uploads a local file and returns a FileRef to pass into a command.
Signature
Needs a Ready session — the upload is created against it. Throws NotFoundError when the path does not exist.
Example
Uploads are passed separately rather than embedded in args. The Python SDK finds a FileRef sitting in the arguments and pulls it out; C++ has no way to recognise one inside a Json, so it is named in the third parameter instead. Explicit costs a few characters and cannot silently miss one.

upload_bytes()

The same result as upload_file(), for a caller who has the bytes rather than a path — a frame just rendered, a buffer just decoded.
Signature
data is borrowed for the call only.
Example

request_schema()

Signature
The model’s command schema, as an OpenAPI document — the same schema published on the Model API Reference pages, fetched from the running model. What to read when a command is rejected: it is the model’s own account of what it accepts, which is more current than any documentation.

Recordings

request_clip()

Asks for a clip covering the last duration_seconds of the session.
Signature
Resolves when the platform has accepted the request, which is not the same as the clip being ready — Clip::download() is what waits for that.
Example
See Recordings for the full flow.

request_recording()

Signature
The same, covering the whole session up to now.

Tracks

track()

The track called name, as a Track — the only way to push frames into one, receive its frames, or pause it.
Signature
Example
Throws NotFoundError, listing what the session does declare, for a name that is not among them. Before the session has declared anything, any name is allowed: there is nothing yet to contradict, and the refusals that matter happen when a handler is registered or a frame is pushed.

tracks()

Every track the session declared, as a TrackList — for discovery, and for a caller who would rather not hardcode a name.
Signature
Example
Empty until the model’s capabilities arrive, shortly after connect().

set_bitrate()

Bounds what the whole connection may allocate, in bits per second.
Signature
Definition
std::optional<std::int32_t>
A floor for the congestion controller; it will not drop below this even on a poor estimate. A floor above what the link can sustain trades graceful degradation for a fixed send rate, so choose it deliberately.
std::optional<std::int32_t>
The initial encoder target. WebRTC starts at ~300 kbps and ramps, which is visible as a few seconds of soft video.
std::optional<std::int32_t>
A ceiling on the whole connection.
There are two bitrate ceilings and they are conjunctive — the lower one wins. This is the connection-wide one. Track::set_bitrate() bounds one sender’s share of it, and that is the one that lifts WebRTC’s 2.5 Mbps video default. Raising max_bps here alone will not make a video track exceed it.
Throws on a session that is not Ready. The bounds outlive a reconnect.

get_stats()

A snapshot of the live connection: RTT, jitter, packet loss, bitrates, the transport ICE selected, and the WebRTC engine’s own per-stream counters.
Signature
Returns a ConnectionStats. Throws on a session that is not Ready.
Example

Events

Every on_* returns a Subscription: an RAII token that unregisters the handler when it goes out of scope.
Discarding the return value unregisters immediately. client.on_status(...); as a statement registers a handler and cancels it on the same line. Hold the token, or call .detach() to say the handler should live as long as the client does. This is the one place the C++ surface diverges from the Python one, which offers off(event, handler) — two std::functions cannot be compared, so a token is the only honest removal.
Signature
Example
Model messages and platform messages are separate events because they are separate things: a caller reading only on_message never has to filter the platform’s out of it.
There is no client-wide frame event. Media is delivered per track, through Track::on_frame() and Track::on_audio() — a single handler fed every incoming track of a kind at once could not tell them apart.
on_error hands you a ReactorError, the same type a failed call throws. Match on code(), or branch on recoverable() when the specific code does not matter. See ReactorError.

time_micros()

The engine’s monotonic clock, in microseconds — the epoch a frame’s capture time is read in.
Signature
Read it once per unit of produced media and stamp every track with that one value: tracks are synchronised by sharing a capture time, not by reaching the encoder at the same moment.
Example
Unrelated to the system clock — a UNIX timestamp is not a substitute.