Skip to main content
Rolling red sand dunes at dusk
New releases, breaking changes, and notable improvements across the Reactor platform: JavaScript SDK, Python SDK, C++ SDK, Swift SDK, and API. Each model keeps its own release notes beside its reference pages, linked as Changelog under that model in Model APIs.
Swift SDK 1.4.0 · reactor-sdk 1.9.0 (Python) · C++ SDK 2.4.0 · Java SDK 1.2.0
Rolls up Swift SDK 1.3.0 and 1.4.0.

New

  • stats() gives you a snapshot of the live connection. It returns a ConnectionStats value with round-trip time, jitter, packet loss, and the measured and estimated bitrates. It also reports the candidate pair that carries the media, plus the engine’s per-stream counters. A field the engine did not measure yet reads nil rather than zero. The call needs a .ready session.
  • The Swift SDK reports connection quality to the runtime every 5 seconds. It sends one reading per track you send or receive, with bitrate, packet loss, jitter and frame rate. It also sends connection-wide readings such as the bandwidth estimate and the round-trip time. The reports need no configuration, and they do not change what stats() gives you. A runtime that does not read them ignores them.
  • The initializer takes autoResumeTracks. It chooses whether the client subscribes to every recvonly track on connect and on reconnect. It defaults to true, which is what the SDK always did, so your code does not change. Set it to false to start unsubscribed, then resume() the tracks you want. A client keeps the choice for its whole life.
  • The Python constructor takes auto_resume_tracks too. It works the same way: it defaults to True, so your code does not change. Pass False to start with every recvonly track paused, then Track.resume() the ones you want.
  • The C++ Options struct takes auto_resume_tracks too. It works the same way: it defaults to true, so your code does not change. Set it to false to start with every recvonly track paused, then Track::resume() the ones you want. The field comes after executor, so an aggregate initializer you wrote before still picks the executor.
  • The Java ReactorOptions builder takes .autoResumeTracks(...) too. It works the same way: it defaults to true, so your code does not change. Pass false to start with every recvonly track paused, then resume() the ones you want.

Fixed

  • A session that negotiates data-channel chunking no longer loses what it sends. A data channel could decide it was not chunked while the runtime’s end of it was. From then on, nothing the client sent arrived, and neither side reported an error. Both ends now agree.
  • The Python SDK gets the same chunking fix. A command that a chunked session dropped without an error now reaches the model, and send_command() gets its reply.
  • The C++ SDK gets the chunking fix as well. A command or message that a chunked session dropped without an error now reaches the model.
  • The Java SDK gets it too. A chunked session no longer drops what sendCommand() sends, so the command reaches the model and its reply comes back.
  • Each frame arrives with its own metadata. The frameID and userData on VideoFrame come from the decoded frame itself. Before, a dropped frame shifted the tags, and every later frame carried the metadata of the frame before it.
reactor-sdk 1.8.0 (Python) · Java SDK 1.1.1 · C++ SDK 2.3.0
Rolls up reactor-sdk 1.7.0 and 1.8.0 (Python), plus Java SDK 1.1.0 and 1.1.1.

New

  • The Python SDK reports connection quality to the runtime every 5 seconds. It sends one reading per track you send or receive, with bitrate, packet loss, jitter and frame rate. It also sends connection-wide readings such as the bandwidth estimate and the round-trip time. The reports need no configuration, and they do not change what get_stats() gives you. A runtime that does not read them ignores them.
  • The Java SDK reports connection quality to the runtime every 5 seconds too. It sends the same per-track and connection-wide readings. They need no configuration, and they do not change what getStats() gives you.
  • The Java SDK takes an API key in ReactorOptions. Set .apiKey(key) and the client exchanges the key for a token itself on each connect(). The token only works with the model you named in the options. Keep the key on a machine you control. A token you set with .jwt(token) takes priority, and the SDK never replaces it.
  • The C++ SDK reports connection quality to the runtime every 5 seconds too. It sends the same per-track and connection-wide readings. They need no configuration, and they do not change what get_stats() gives you.

Changed

  • send_command() sends a payload larger than 256 KiB, up to 64 MiB. The SDK asks for data-channel chunking when it connects. When the runtime accepts, a large payload goes through instead of failing with MESSAGE_TOO_LARGE. A runtime that does not answer keeps the 256 KiB limit, so a session with one works as it did before. Commands of every size also reach the model in fewer round trips. Your code does not change.
  • sendCommand() in the Java SDK sends a payload larger than 256 KiB, up to 64 MiB. It asks for the same data-channel chunking, and a runtime that does not answer keeps the 256 KiB limit. Your code does not change.

Fixed

  • Each frame arrives with its own metadata. The user_data argument that on_frame() and on_raw_frame() pass comes from the decoded frame itself. Before, a dropped frame shifted the tags, and every later frame carried the metadata of the frame before it. The SDK drops a frame after a decode error, while it waits for a keyframe, or when the frame arrives late.
  • Each frame arrives with its own metadata in the Java SDK too. VideoFrame.userData() comes from the decoded frame, not from the frame before it.
  • Each frame arrives with its own metadata in the C++ SDK too. The user_data and frame_id on VideoFrame come from the decoded frame, not from the frame before it.
js-sdk 3.1.0 · C++ SDK 2.2.0 · Swift SDK 1.2.0
Rolls up js-sdk 3.1.0, C++ SDK 2.2.0, and Swift SDK 1.2.0.

New

  • The SDK reports connection quality to the runtime every 5 seconds. It sends one reading per track you send or receive, with bitrate, packet loss, jitter and frame rate, plus connection-wide readings such as the bandwidth estimate and the round-trip time. The reports need no configuration, and they do not change what getStats() and statsUpdate give you, or how often. A runtime that does not read them ignores them.

Changed

  • sendCommand() sends a payload larger than 256 KiB, up to 64 MiB. The SDK asks for data-channel chunking when it connects. When the runtime accepts, a large payload goes through instead of reporting MESSAGE_TOO_LARGE. A runtime that does not answer keeps the 256 KiB limit, so a session with one works as it did before. Your code does not change.
  • send_command() sends a payload larger than 256 KiB, up to 64 MiB. The SDK asks for data-channel chunking when it connects. When the runtime accepts, a large payload goes through instead of failing with MESSAGE_TOO_LARGE. A runtime that does not answer keeps the 256 KiB limit, so a session with one works as it did before. Commands of every size also reach the model in fewer round trips. Your code does not change.
  • sendCommand(_:_:uploads:) sends a payload larger than 256 KiB, up to 64 MiB. The SDK asks for data-channel chunking when it connects. When the runtime accepts, a large payload goes through instead of failing with MESSAGE_TOO_LARGE. A runtime that does not answer keeps the 256 KiB limit, so a session with one works as it did before. Commands of every size also reach the model in fewer round trips. Your code does not change.

Fixed

  • getStats() and statsUpdate describe the network path the session really uses. The round-trip time, the bandwidth estimates and the bitrates now come from the candidate pair that carries the media. When the browser nominated more than one pair, which is common on a machine with several network interfaces, those values could come from an idle pair: no bandwidth estimate appeared, and the bitrates sat near zero.
reactor-sdk 1.6.0 (Python) · Swift SDK 1.1.0 · C++ SDK 2.1.0
Rolls up reactor-sdk 1.6.0 (Python), Swift SDK 1.1.0, and C++ SDK 2.1.0.

New

  • connect() asks for two WebRTC setup extensions, SNAP and SPED. Together they take up to three round trips off the time to the first frame. Your code does not change. A server that does not answer them negotiates the ordinary handshake, so a session with that server works as it did before.
  • connect() asks for two WebRTC setup extensions, SNAP and SPED. Together they take up to three round trips off the time to the first frame. Your code does not change. A server that does not answer them negotiates the ordinary handshake, so a session with one works as it did before.

Changed

  • The Swift SDK does not log a debug line for each frame on a track with no handler. Before, a client that reads one track of several got one line per frame, in the media category of the inc.reactor.sdk log. The SDK now logs only a frame that arrives with no track name, which means the transport could not match it to a declared track. Frame delivery itself did not change.

Fixed

  • connect() and reconnect() report the real reason when the transport fails while they run. Both raise DisconnectedError now, and its message names the transport state that ended the connection. recoverable is True, so a retry can succeed. Before, that race raised a generic error about a transport that was not prepared.
  • connect() and reconnect() report the real reason when the transport fails while they run. Both throw ReactorError.disconnected now, and its message names the transport state that ended the connection. recoverable is true, so the call can succeed on a retry. Before, that race threw a generic error about a transport that was not prepared.
  • A Subscription that goes out of scope waits for a delivery already in flight. remove() and the destructor now block until a call to that handler on another thread returns. Whatever the handler captured is then safe to destroy. Before, a caller could destroy those captures while that call was still in progress. A handler that removes its own subscription does not wait for itself.
reactor-sdk 1.5.1 (Python) · C++ SDK 2.0.2 · Swift SDK 1.0.1
Rolls up reactor-sdk 1.5.1 (Python), C++ SDK 2.0.2, and Swift SDK 1.0.1.

New

  • pushFrame() on an audio track takes any common capture rate, in mono or stereo. Pass 8000, 16000, 24000, 32000, 44100 or 48000 Hz and 1 or 2 channels, and the SDK resamples locally to what the session carries. Before, you had to resample to 48 kHz mono yourself. A rate or channel count outside those sets now throws ReactorError.badRequest, which names what is allowed.

Changed

  • An audio format the SDK cannot send raises ValueError. push_frame() refuses a sample_rate outside the six rates above, and a num_channels other than 1 or 2. It also refuses PCM whose length is not exactly samples_per_channel * num_channels * 2 bytes. Raw BGRA video bytes already had that check against their dimensions. Microphone checks its capture rate and channel count too, before it opens the device. Before, the SDK took a format it could not carry and sent noise.

Fixed

  • push_frame() keeps an audio frame’s sample rate and channel count. Before, every buffer went on the wire as 48 kHz mono, so PCM captured at another format arrived at the wrong pitch and length. 8000, 16000, 24000, 32000, 44100, and 48000 Hz all work now, in mono or stereo. WebRTC resamples locally to the format the connection negotiated. Push at your capture rate: the SDK buffers a chunk of any size into 10 ms blocks, and a format change or a disconnect drops the partial block.
  • push_frame() keeps the sample rate and channel count you give it. An audio track takes 8, 16, 24, 32, 44.1 or 48 kHz PCM, in mono or stereo, and WebRTC resamples it locally. Before, the SDK sent every buffer as 48 kHz mono, so PCM captured at any other rate reached the model distorted. Pace your pushes at the capture rate.
  • A rate or a channel count the SDK cannot take now throws BadRequestError. Before, the call returned as though it worked, and the audio never arrived.
reactor-sdk 1.5.0 (Python) · js-sdk 3.0.2 · C++ SDK 2.0.1
Rolls up reactor-sdk 1.5.0 (Python), js-sdk 3.0.2, and the C++ SDK’s 2.0.0 and 2.0.1 releases.

Breaking

  • Audio tracks use on_frame() and push_frame(), as video already did. on_audio() and push_audio() are gone. Take an AudioFrame in the handler you pass to on_frame(), and push interleaved 16-bit PCM with push_frame(Samples{...}). The overload has to match the track’s kind(), so a video handler on an audio track throws InvalidStateError. Rename the two calls. Nothing else about them changed.
  • pause() and resume() throw on a sendonly track. Both control what the client receives, and a sendonly audio track now refuses them exactly as a video one already did. Code that paused a track it publishes did nothing before, and throws now.
  • Options::local skips the API-key exchange and selects localhost. With api_url left at its default, a local connection goes to reactor::LOCAL_API_URL (http://localhost:8080) rather than to production. A key you passed is no longer exchanged for a token. A custom api_url is kept as it is, and so is a JWT you supplied. This matches the Python SDK.

New

  • Wheels for Alpine Linux. pip install reactor-sdk now finds a wheel on a musl 1.2+ distribution, x86_64 and aarch64. Before, there was none, and pip resolved to an older release with a different API. Pin reactor-sdk>=1.0 to get an error instead.
  • fetch_jwt(). Exchanges an API key for a token without building a client or opening a session, for a backend that mints tokens somebody else will use. FetchJwtOptions scopes the token to a set of models, caps how many sessions it may open and how long each one runs, and sets its lifetime. It returns std::future<std::string>, and a failure arrives as the same typed ReactorError a call throws.
  • A constructor that takes no credential. reactor::Reactor{model, options} with options.local = true connects to a local runtime, which needs none.
  • on_capabilities_received() and on_session_id_changed(). The model’s capabilities as they arrive, and the session id whenever it is set or cleared (std::nullopt). Register both before connect() to catch the first of each. The declared tracks are refreshed before capabilities are delivered, so tracks() is already current inside that handler.
  • A FileRef can go inside a command’s arguments. A parameter that takes several files has no named slot in the uploads map. Pass those references in args instead: a FileRef converts to Json as the upload reference the model reads, and Json::array({first, second}) is that list. A parameter that takes one file is still named in uploads.

Fixed

  • A FileRef nested inside send_command()’s data now reaches the model. Put one in a list or a dict, and the SDK sends it as the upload reference the model reads. Before, the call raised TypeError, so a parameter that takes several files needed a hand-written {"upload_id": ...} dict, which you can now delete. A top-level FileRef still travels beside the arguments.
  • A FileRef nested inside sendCommand()’s data now reaches the model. Pass one inside an array or object, and the SDK sends it as the upload reference the model reads. Before, it crossed the wire with the class’s own field names (uploadId, mimeType), and the model refused it. A parameter that takes several files needed a hand-written { upload_id } object, which you can now delete. A top-level FileRef still travels beside the arguments.
C++ SDK 1.0.0 · reactor-sdk 1.3.0 → 1.4.0 (Python)
Rolls up the C++ SDK’s first release and reactor-sdk 1.3.0 and 1.4.0.

New

  • The Reactor C++ SDK’s first release. A C++17 client for native applications (engines, capture pipelines, desktop clients), built on the same Rust core the Python and JavaScript SDKs bind, and shipped as one prebuilt archive per platform rather than through a package manager. The base Reactor class and Track cover connecting, commands, uploads, recordings, events, connection-wide and per-track bitrate control, and connection stats — calls return std::future<T>, frames arrive as BGRA on the library’s delivery thread. Speaker and Microphone (reactor::sdk_audio) play a recvonly audio track through real speakers or capture a real microphone into a sendonly one. See Installation and Using the SDK.
  • get_stats(). A snapshot of the live connection, as a ConnectionStats: RTT, jitter, packet loss, bitrates, and the WebRTC engine’s own per-stream counters. An unmeasured field reads None rather than zero. The SDK derives the two measured bitrates against the previous call, so they stay None until a second call at least 200 ms later. Only callable once the session is ready.
  • Reactor.set_bitrate() and Track.set_bitrate(). Bound the connection’s whole budget, and one sender’s share of it, in bits per second. The two apply together and the lower wins. For video, the track-level ceiling is the one to raise: with nothing set, WebRTC caps anything above 960x540 at 2.5 Mbps, 4K included.
  • max_session_duration_seconds on the Reactor() constructor. Terminates every session the client’s own minted token creates after that many seconds, whatever the token’s expiry says. The same constraint fetch_jwt() takes, now available without minting the token yourself. Ignored when you pass jwt.
  • RecorderDisabledError. request_clip() / request_recording() raise this instead of a bare ReactorError when the model’s recorder is disabled or has crashed. Retrying the call will not help. The recorder has to be restarted on the model side. See Error codes.
  • get_stats() reports the transport the session uses. candidate_type is "host", "srflx", "prflx", or "relay", and "relay" means the media goes through a TURN server. That is the first thing to check when latency is bad. relay_protocol names that server’s own transport ("udp", "tcp", or "tls").
  • Three fields the JavaScript SDK already reported are here too. available_incoming_bitrate_bps and available_outgoing_bitrate_bps carry the congestion controller’s estimate of what the path can carry, not what flows over it. frames_per_second is the rate on the received video stream.
  • The per-stream report carries more. InboundStream and OutboundStream now name their kind ("audio" or "video") and count video frames. A send stream also carries what the far end reports back about it: total_round_trip_time_s, fraction_lost, and packets_lost. CandidatePair gains nominated, writable, its own byte and packet totals, and the two bitrate estimates.

Changed

  • close() settles operations still in flight. A send_command() whose reply had not arrived when close() ran now raises AbortedError in the coroutine that awaits it. Before, that await stayed pending for the life of the process. disconnect() was never affected.
  • push_frame() refuses raw bytes that do not match their dimensions. A BGRA buffer that is not exactly width * height * 4 bytes raises ValueError. Only the dimensions cross to the native side, never the length, so a mismatch used to send the reader past the end of the buffer. Frames pushed as a numpy array are unaffected.
  • Prebuilt wheels. pip install reactor-sdk now installs a wheel with the native library already in it, one per platform: Linux on glibc 2.34 or newer (x86_64, aarch64), macOS 11+ on arm64 and 13+ on x86_64, and Windows x86_64. One wheel covers every supported interpreter, 3.10 and up. There is no source distribution, so anywhere else pip quietly resolves to an older release. Pin reactor-sdk>=1.0 to get an error instead.
  • jitter_s and packet_loss_ratio now come from the received video stream. That is the stream the JavaScript SDK reads, so the two SDKs report the same number for the same connection. They used to aggregate across every receive stream. An audio-only session still gets both: they fall back to the receive streams there are, where the JavaScript SDK reports nothing.
  • The two measured bitrates now cover the candidate pair that carries the media. incoming_bitrate_bps and outgoing_bitrate_bps count everything that pair carried, RTCP and the data channel included. Before, they summed RTP payload and read a little low. One consequence: both read None until ICE selects a pair, on top of the cases that already read None.
  • rtt_ms comes from that same pair, rather than from the highest-priority succeeded one.

Fixed

  • An outbound stream’s round_trip_time_s was always 0.0. It now carries the measured value.
  • A send stream’s packets_sent and retransmitted_packets_sent wrapped after about 4.3 billion packets, which is roughly seven weeks at a thousand packets a second. After the wrap, a cumulative counter appeared to go backwards. Both are 64-bit now, as are the candidate pair’s.
js-sdk 3.0.1

New

  • setTrackBitrate() raises the ceiling on a track you publish. With no bound set, a Chromium-based browser caps a sender above 960x540 at 2500 kbps, so a 720p or 1080p publish never exceeds 2.5 Mbps even on a fast connection. Call it with maxBps after publishTrack() to give the encoder more room. minBps sets a floor, which only Chromium honors. There is no connection-wide equivalent, because browsers do not expose one.
js-sdk 3.0.0
A major version bump for @reactor-team/js-sdk. This release moves session and protocol handling into the same underlying implementation the Python and C++ SDKs share — the SDK’s own JavaScript/TypeScript no longer drives the browser’s WebRTC APIs by hand; that layer does, over the same wire protocol.

Breaking

  • sendCommand() now waits for its reply, and resolves with it. The returned promise used to resolve as soon as the command was sent, before the model replied. It now genuinely awaits the model’s correlated reply, bounded by a timeout, and resolves with the reply payload (or undefined if the handler acknowledged the command without sending one back). A call site that awaits or otherwise consumes the returned promise now waits for the real round trip instead of resolving almost immediately; a caller that never awaited it in the first place is unaffected.
  • ReactorError is a typed class hierarchy, not a flat object. It’s now the base of 16 concrete subclasses (UnauthorizedError, ConflictError, RateLimitedError, and more — see Error codes), each carrying a stable code, recoverable, status, operation, retry_after_ms, and timestamp_ms. The old component field ("api"/"gpu") is gone. retryAfter/timestamp are kept alongside the new field names for compatibility.

New

  • Track introspection on the base class. tracks(), trackMapping(), pausedTracks(), and getTrackByName()/getStreamByName() / getTrackByMid()/getStreamByMid() resolve a named or negotiated track to its MediaStreamTrack/MediaStream directly off a Reactor instance.
  • pauseTrack() / resumeTrack() are asynchronous. Both now return a Promise<void>, queued behind the same control round-trip as other track operations, instead of applying synchronously.
  • getSchema() / schemaReceived. The model’s command schema, fetched automatically on the first "ready" transition and cached for synchronous reads.
  • once(). Registers an event listener that fires at most once, then removes itself.
  • isFileRef(). A structural check for anything shaped like a FileRef, for callers who’d rather duck-type than rely on instanceof.
  • useReactor() exposes more store state. lastMessage, jwtToken, and connectOptions are now readable off the store, and pauseTrack/resumeTrack are store actions alongside publish/unpublish.

Removed

  • getState(). Read getStatus(), getLastError(), and getSessionId() individually instead.
  • The sessionExpirationChanged event. It never carried a real value in any deployment — dropped rather than carried forward.
reactor-sdk 1.0.0 (Python)

Breaking

  • send_command() now waits for its reply. Previously fire-and-forget — the call resolved as soon as the command was queued, and any reply from the model arrived later as a separate message event. It now returns the model’s correlated reply directly: {"type": ..., "data": ...}, or None if the handler acknowledged the command without returning a message (an auto-generated set_<field> setter, for instance). To fire a command without waiting on the reply, schedule the call instead of awaiting it directly — asyncio.create_task(reactor.send_command(...)) — keeping a reference to the task so it isn’t garbage-collected before it completes.
  • Errors are now typed exceptions, and ReactorError is the one class for both. Every failure used to arrive as one bare exception carrying only a sentence, and separately, on_error handed you a same-shaped-but-different dataclass. ReactorError is now both: the base of 16 concrete subclasses (UnauthorizedError, ConflictError, RateLimitedError, and more — see Error codes) that on_error and a raised call hand you identically, each with a stable code, recoverable, status, operation, retry_after_ms, and (event-only) timestamp_ms. The old component field (api/gpu) is gone — it split codes by platform tier, which wasn’t actionable and could report the same failure under two different codes. except ReactorError still catches everything; code relying on error.component needs updating.
  • Audio devices are synthetic-only. The constructor’s adm_mode argument is gone; no Reactor instance ever opens a real microphone or speaker on its own. A sendonly audio track only ever carries PCM you push yourself with Track.push_frame(), and a model’s audio must be played back explicitly via Track.on_frame() / on_raw_frame() — or with Speaker/Microphone, below.
  • reactor.on_frame (client-wide) is removed. It only ever worked for video, and a single handler fed every recvonly video track couldn’t tell them apart. Register on the Track instead, found by name (reactor.track(name)) or by filtering reactor.tracks (reactor.tracks.with_direction("recvonly").with_kind("video").one()).
  • The client-wide on("frame", ...) / on("audio", ...) events are removed too, for the same reason. on() raises ValueError at registration for either name, naming the Track method to use instead — on_frame() for decoded frames, on_raw_frame() for the same bytes with no NumPy conversion.
  • publish_track() / unpublish_track() are the only name-based track methods still public on Reactor. push_video_frame(), push_audio_frame(), pause_track(), and resume_track() are now internal — reach the same operations through the Track object instead: track.push_frame(), track.pause(), track.resume(). publish_track() already hands back that same Track.
  • Track.push_frame() now raises InvalidStateError on an unpublished sendonly track, rather than silently dropping the frame. Call publish() first — Track.published says whether that’s already happened.
  • reconnect() resumes recvonly tracks automatically, but does not restore a sendonly track’s publish. Call publish() again after reconnecting for anything you were sending before it.
  • @reactor.on_track hands over the Track itself, not a bare (name, mid). The Track is already resolved — reactor.track(name) done for you — so a handler that wants to push frames or register on_frame no longer has to look it up first; track.mid still carries the WebRTC media stream ID.
  • unpublish_track() / Track.unpublish() return None, not an int. A failure is now logged (reactor_sdk at WARNING) rather than left for the caller to notice by checking a return value nobody checked — unpublish is commonly the last call in a finally block, so it still does not raise.
  • Reactor()’s positional arguments are model_name, api_key — api_url moved to keyword-only. Matches the old py-sdk’s own constructor order exactly, so Reactor(model, key) ported from there needs no rewrite to keyword arguments at all. Previously api_url/model_name were positional (in either order, sniffed by which one looked like a URL); any call relying on that specifically needs updating, but a plain Reactor("your-model-name") is unaffected.
  • upload_file() now requires ready. Previously allowed as soon as the session was created, before the WebRTC handshake finished — raises InvalidStateError now if called earlier, the same guard request_clip() and Track.pause() already used. See File Uploads.

New

  • reactor.tracks is now a TrackList. Still a plain list[Track] for iterating and indexing, with with_kind() / with_direction() filters that chain, and .one() for the common case of “the track I mean, and an error if that’s ambiguous.” See TrackList.
  • The Track object. reactor.track(name) and reactor.tracks return a Track scoped to one named slot, with publish(), push_frame(), on_frame()/on_raw_frame(), and pause()/resume() that raise on a direction mismatch instead of a silent no-op.
  • Track.published. Whether a sendonly track is currently activated — readable state for something the session itself doesn’t track, cleared automatically whenever the connection leaves ready.
  • download_clip(). Fetches every segment a Clip’s playlist_url names. Given a path, streams straight to it (returns None, never holds more than one segment in memory — the one to use for request_recording()); without one, returns the assembled bytes instead. Takes an optional on_progress callback either way. Closes a real gap left by the rewrite off the old py-sdk, which had this as download_clip_as_file().
  • reactor.download_clip(seconds) / reactor.download_recording(). request_clip() / request_recording() plus the download above, in one call, for when the file is all you want. request_clip() / request_recording() still return just the Clip for anyone who also wants its session_id, markers, or predicted_ready_at_ms.
  • connect(connection_id=...). Adopts a WebRTC connection slot a backend already registered for a session, the connection-level analogue of session_id adoption — see Multiple connections per session. Also closes a gap left by the rewrite: the old py-sdk had this, and the JavaScript SDK still does.
  • reconnect() now works from ready, not only after a drop. It tears the live connection down itself first — without ending the session, unlike disconnect() — so there was never a need to call disconnect() immediately before it; that combination is also no longer required. disconnect()’s own behavior is unchanged — it has always ended the session server-side, with no recoverable option — a few places in these docs previously described it backwards, now corrected. See Sessions.
  • Speaker / Microphone (reactor_sdk.audio_devices). Play a recvonly audio track through real speakers, or capture a real microphone into a sendonly track — context managers wrapping a sounddevice stream around a Track: with Speaker(output_track), Microphone(mic_track): .... Not re-exported from reactor_sdk — the only part of this SDK with a runtime dependency, so pip install "reactor-sdk[audio]" and import from reactor_sdk.audio_devices directly. Speaker promotes what examples/pygame_app had hand-rolled; Microphone is new.
  • fetch_jwt(api_key, api_url=...)’s api_url is optional now, defaulting to the same production API Reactor() itself defaults to. Pass it explicitly only when minting a token against a different one.
js-sdk · Multi-client connections

Breaking

  • Publishing an input track is now explicit. Output tracks still stream automatically on connect, but sendonly (input) tracks send nothing until your app calls publishTrack() (or renders <WebcamStream>). Apps that relied on input media flowing at connect must now publish explicitly once the connection is ready. See Tracks.

New

  • Output track pause/resume. New pauseTrack() and resumeTrack() methods, plus an autoResumeTracks connect option (default true), control when each output track streams.
  • Multi-client sessions. A single session can now back several connections at once. connect() accepts sessionId and connectionId options to attach to a session or connection your backend created.
Pricing

Changed

  • Pricing shown per second, in dollars. Model pricing on the Pricing & Billing page now leads with a per-second US-dollar rate, matching how sessions are billed, with a per-hour figure shown for reference.
Docs · Pricing & billing

New

  • Pricing & billing docs. Published Pricing & Billing, covering session pricing, idle vs. active billing, and recoverable-state behavior.
  • Beta status callouts. Added a banner across docs and the Overview noting that Reactor is in beta. APIs and pricing may evolve.
Authentication · API

New

  • Token TTL with expires_after. Auth tokens now carry an expires_after field so clients can refresh proactively instead of waiting for a 401. See Authentication.
js-sdk · reactor-sdk · Concepts

New

  • File uploads in the Python SDK. reactor-sdk now supports upload_file() returning a FileRef that can be passed into send_command().
  • File uploads in the JavaScript SDK. uploadFile() and FileRef are available in @reactor-team/js-sdk for passing files (images, audio, blobs) as command arguments.
  • New concept page: File Uploads documents the upload-then-reference pattern in both SDKs.
API

Breaking

  • Token endpoint is now POST. The auth-token endpoint moved from GET to POST to allow request-body parameters. Update any direct API callers; the SDKs handle this automatically once upgraded.
js-sdk 2.7 · reactor-sdk 0.4

Breaking

  • New SDK protocol. Wire-level protocol overhaul shipped in @reactor-team/js-sdk 2.7 and reactor-sdk 0.4. Older SDK versions will no longer connect. Upgrade to the latest release.
  • Removed fetchInsecureToken() and JWT helpers. The development-only insecure token helpers were removed; all auth now flows through the standard token endpoint.

Improved

  • Maple theme refresh. Docs site updated to Mintlify’s Maple theme with Lucide icons, new codeblock styling, and refreshed colors.
  • Navigation split into separate “SDK” and “API Reference” tabs.
js-sdk 2.6.0

Breaking

  • API renames in @reactor-team/js-sdk 2.6.0. Several public methods and types were renamed for consistency with the Python SDK. Update import paths and call sites.
Docs

Improved

  • Renamed “Realtime AI” → “Realtime Video AI” across the docs to better reflect what Reactor does. No API or SDK changes.
reactor-sdk (Python)

New

  • Python SDK launched. reactor-sdk is now available on PyPI, async-first, with parity for sessions, commands, and message handling. See Using the SDK.
js-sdk

Breaking

  • sendCommand() replaces sendMessage(). The command API was renamed to clarify the distinction between commands (client → model) and messages (model → client). Update all sendMessage call sites. See Commands and messages.
  • Removed message queueing. Commands now have explicit waiting state instead of implicit queueing. Applications can decide whether to drop, replace, or buffer in-flight commands.
Authentication · API

Breaking

  • JWT auth replaces insecureApikey. All client connections now require a short-lived JWT minted server-side from your API key. The insecureApikey development flow is removed.
  • New API Reference section. Published a complete API Reference covering the Reactor class, React components, hooks, types, and events.
Docs

New

  • Reactor docs site launched. Initial release with Overview and Quickstart. Ported from the legacy readme.io site.