Streams
mr-data stream registers the connector document, accepts it, records a bounded window or keeps listening, and names the recording a recipe binds.
stream
Section titled “stream”mr-data stream {document,series,registry,probe,capture,subscribe,subscription,status,recordings} [WORD] [ID] [options] [--json]A websocket venue’s bytes do not exist until somebody records them. One name carries nine verbs.
A build reads recorded batches and takes no duration of its own. The recipe names the recording rather than the venue, so a run never holds a socket open.
The order:
- Register the connector document.
- Create a registry that accepts it.
probeit to see what it classifies.capturea bounded window, orsubscribeto keep listening.recordingsto see what a build would bind.- Name the digest in the recipe.
| Verb | WORD |
ID |
What it does |
|---|---|---|---|
document |
register or show |
document id, with show |
Register a connector document, or read one back. |
series |
register |
none | Register the rotating roster a subscription follows. |
registry |
create |
none | Accept a set of documents. |
probe |
none | none | Listen briefly and record nothing. |
capture |
none | none | Record one bounded window and store it. |
subscribe |
none | none | Keep listening, checkpointing what a build continues from. |
subscription |
show, pause, resume, stop |
subscription id | Read one, or move it. |
status |
the capture or probe id | none | Read a capture or a probe back. |
recordings |
none | none | Name what a build would bind for one venue, and until when. |
Options
Section titled “Options”| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
--json |
none | no | One JSON object instead of human lines. | off |
--file |
FILE |
with document register, series register |
The document to register, as a path to a JSON file. A connector document is at most 256 KiB, a series document at most 64 KiB. The reader is strict JSON, and a connector document carries no fractional number anywhere. | none |
--name |
NAME |
no | With document register or series register, the name to hold the registered document under. It starts with a lowercase letter, carries lowercase letters, digits, dots and dashes, ends .json, and is at most 133 characters including that suffix. |
derived from what the document calls itself |
--digest |
DIGEST |
with registry create |
One registered connector-document digest, repeatable up to 32 times. With series register, the one connector document the series rotates members of. That creates one registry over the document and the series together. |
none |
--document-digest |
DIGEST |
probe, capture, subscribe, recordings |
The connector document to act against, by the digest its registration printed. | none |
--registry |
REGISTRY |
probe, capture, subscribe |
The registry that holds the document, by the identifier its creation printed. | none |
--series |
SERIES |
no | With subscribe, the series whose roster this subscription follows, by the id the series document declares. Lowercase letters, digits and dashes, starting with a letter, at most 32 characters. |
none |
--member |
MEMBER |
probe and capture. With subscribe, unless --series |
One member of the venue to listen to: a channel, a ticker, a market. Repeatable, up to 256, and each one distinct. A probe and a capture name their members outright and take no --series. |
none |
--seconds |
N |
one of these three, with capture |
How long to record for, 1 to 600. | none |
--max-events |
N |
one of these three, with capture |
Stop after this many messages, 1 to 10000. | none |
--max-bytes |
N |
one of these three, with capture |
Stop after this many recorded bytes, 1 to 8388608. | none |
--secret |
NAME |
no | The stored credential the venue authenticates with, by the name mr-data keys set printed. The value never appears here and never leaves the vault. |
none |
--kind |
capture or probe |
no | With status, which resource the identifier names. A probe and a capture live on separate routes, and an identifier does not say which. |
capture |
--watch |
none | no | With capture, keep reading the record back until it stops, and print what it stopped on. The capture runs under the bound it was given either way. |
off |
A subscription names its members outright or names a series, never both. Naming both, or neither, returns an error before Studio resolves a credential.
A capture states at least one stopping condition. Without one, the socket would remain open. The three ceilings are hard recorder limits. A request above a limit returns an error instead of being lowered.
A member name is [A-Za-z0-9][A-Za-z0-9._:@/-]{0,255}. The client checks it, so a mistyped member never becomes a 422 from Studio.
JSON shapes
Section titled “JSON shapes”{ "schema_version": "mostlyright-thin-client-v4-stream-document.v2", "status": "stream_document_registered", "lane": "hosted", "stream_connector_document_id": "4d6e9b05-2f71-48c3-b05a-7e1c9d2a6f83", "document": { "document_name": "kalshi.json", "document_digest": "9f3c…", "document_kind": "connector", "connector_id": "kalshi-markets", "series_id": null, "endpoint_host": "api.elections.kalshi.com", "created_at": "2026-09-12T09:00:00Z" }, "recipe_reference": { "stream_connector_document_digest": "9f3c…" }, "note": "The document is admitted. A recipe names this venue by its digest … and a build reads the batches a capture or a subscription sealed under it rather than reaching the venue itself."}Values are illustrative. document show answers the same schema with status: "stream_document_reported" and no recipe_reference. The server computes the digest over its own canonical form. This client computes none, as with recipe registration.
{ "schema_version": "mostlyright-thin-client-v4-stream-registry.v2", "status": "stream_registry_created", "lane": "hosted", "stream_connector_registry_id": "7e3a1c60-5b84-4297-a1d3-0f6b8c4e2d59", "document_digests": ["9f3c…", "a1b2…"]}{ "schema_version": "mostlyright-thin-client-v4-stream-series.v1", "status": "stream_series_registered", "lane": "hosted", "stream_connector_document_id": "b2c3d4e5-6f70-4182-93a4-5b6c7d8e9f01", "document_digest": "b3c4…", "series_id": "kalshi-btc-hourly", "stream_connector_registry_id": "7e3a1c60-5b84-4297-a1d3-0f6b8c4e2d59", "note": "The series is admitted. A subscription follows it by naming the series id and a registry that holds both this document and the connector document it rotates."}{ "schema_version": "mostlyright-thin-client-v4-stream-probe.v1", "status": "stream_probe_started", "lane": "hosted", "probe": { "probe_id": "c8d92f41-0a63-4b57-8e94-1d5f7a0c3b62", "status": "queued", "connector_document_digest": "9f3c…", "stream_connector_registry_id": "7e3a1c60-5b84-4297-a1d3-0f6b8c4e2d59", "members": ["BTC-15M"], "secret_name": "kalshi-ws" }, "note": "A probe seals nothing: it reports which message types the document classified and which it did not. mr-data stream status c8d92f41-0a63-4b57-8e94-1d5f7a0c3b62 --kind probe reads it back."}{ "schema_version": "mostlyright-thin-client-v4-stream-capture.v1", "status": "stream_capture_settled", "lane": "hosted", "polls": 12, "capture": { "capture_id": "1b7f4e28-6c05-4d39-b82f-9a3e0d5c7146", "status": "succeeded", "desired_state": null, "connector_document_digest": "9f3c…", "connector_config_digest": "c5d6…", "stream_connector_registry_id": "7e3a1c60-5b84-4297-a1d3-0f6b8c4e2d59", "members": ["BTC-15M"], "until": "2026-09-12T09:01:00Z", "secret_name": "kalshi-ws", "tripped_reason": null, "sealed_sources": [], "event_shape_descriptor": {}, "bookmark": null, "expected_watermark": null }}Without --watch the status reads stream_capture_started, and the payload carries a note naming the two commands that read it back. --watch keeps asking while the resource says queued, dispatched, claimed or running, and stops on anything else. A word this build does not know ends the wait, and the command prints it rather than spinning.
{ "schema_version": "mostlyright-thin-client-v4-stream-subscription.v2", "status": "stream_subscription_started", "lane": "hosted", "subscription": { "stream_subscription_id": "e04a7c93-1b28-4f65-9d07-3c5e8b1a2f64", "status": "running", "series_id": "kalshi-btc-hourly", "…": "…" }, "note": "A subscription keeps recording until it is stopped. Each build reads forward from the bookmark the last one left, so a refresh continues rather than refetching."}{ "schema_version": "mostlyright-thin-client-v4-stream-subscription.v2", "status": "stream_subscription_reported", "lane": "hosted", "subscription": { "stream_subscription_id": "e04a7c93-1b28-4f65-9d07-3c5e8b1a2f64", "…": "…" }, "recording": { "status": "running", "desired_state": "running", "series_id": "kalshi-btc-hourly", "members_now": 173, "members": ["KXBTCD-26SEP12-B1", "…"], "members_not_shown": 165, "last_data_at": "2026-09-12T09:00:12Z", "last_discovery_at": "2026-09-12T08:00:00Z", "next_discovery_at": "2026-09-12T10:00:00Z", "next_boundary_at": null, "gap_count": 0, "last_gap": null, "bound_table_ids": ["3f8c2d10-6a4b-4c8e-9d1f-5b7a0c2e4d68"], "checkpoint_sequence": 41008, "current_cursor": "…" }}The command counts and samples the roster rather than printing it whole. A series can hold 256 contracts open, and the first eight plus a count is what somebody reads this for. status is stream_subscription_reported, _paused, _resumed or _stopped, one word each for reading one, holding it, letting it run again and ending it.
{ "schema_version": "mostlyright-thin-client-v4-stream-status.v1", "status": "stream_status_reported", "lane": "hosted", "kind": "capture", "capture": { "capture_id": "1b7f4e28-6c05-4d39-b82f-9a3e0d5c7146", "status": "succeeded", "…": "…" }}The payload carries the resource under a key named after kind, so a probe answers "kind": "probe" with a probe object.
{ "schema_version": "mostlyright-thin-client-v4-stream-recordings.v1", "status": "stream_recordings_listed", "lane": "hosted", "connector_document_digest": "9f3c…", "count": 3, "recordings": { "recording 1": { "expires_at": "2026-09-19T09:00:00Z", "…": "…" } }, "note": "A batch that has aged out is refused before a worker is claimed rather than midway through a build, so a recording listed here with an expires_at in the past is a recording to replace rather than to name."}Errors
Section titled “Errors”| Code | When |
|---|---|
THIN_REQUEST_INVALID |
A missing --file, --digest, --document-digest or --registry. A registry with no digests, with more than 32, or with a duplicate. series register --digest naming more than one connector. A subscription naming both --series and --member, or neither. A member name outside the grammar, the same member named twice, more than 256 of them, or none at all. A series id outside its grammar. A capture with no bound at all, or a --seconds, --max-events or --max-bytes outside 1 to its ceiling. A --file that is not one readable strict-JSON object, or is over its byte ceiling. A WORD that is not one this verb takes. |
THIN_RESPONSE_INVALID |
Studio started a probe, capture or subscription without identifying it. |
THIN_SUBSCRIPTION_REQUIRED |
A build named a recording that no subscription is producing. |
THIN_STREAM_UNAVAILABLE, THIN_STREAM_REFUSED |
The venue refused, or nothing could reach it. |
THIN_STUDIO_* |
The venue controller’s own refusals, carried through under their own codes. |
Example
Section titled “Example”DOC=$(mr-data stream document register --file kalshi.json --name kalshi.json --json | jq -r .document.document_digest)REG=$(mr-data stream registry create --digest "$DOC" --json | jq -r .stream_connector_registry_id)
mr-data stream probe --document-digest "$DOC" --registry "$REG" --member BTC-15M --secret kalshi-ws --json
mr-data stream capture --document-digest "$DOC" --registry "$REG" --member BTC-15M \ --secret kalshi-ws --seconds 60 --max-events 5000 --watch --json
mr-data stream recordings --document-digest "$DOC" --jsonThen name the venue in the recipe as {"stream_connector_document_digest": "…"}, the digest rather than the endpoint.
See also
Section titled “See also”- CLI reference: conventions, exit codes, environment variables
- Recipes and sources:
keysstores the shaped credential a venue authenticates with - Tables: the tables a subscription’s recordings keep fresh