# Streams

Source: https://mostlyright.md/docs/reference/cli/stream/

`mr-data stream` registers the connector document, accepts it, records a bounded window or keeps listening, and names the recording a recipe binds.

## stream

```bash
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:

1. Register the connector document.
2. Create a registry that accepts it.
3. `probe` it to see what it classifies.
4. `capture` a bounded window, or `subscribe` to keep listening.
5. `recordings` to see what a build would bind.
6. Name the digest in the recipe.

### Verbs

| 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

| 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

```json title="mr-data stream document register --file connector.json --json"
{
  "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.

```json title="mr-data stream registry create --digest … --json"
{
  "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…"]
}
```

```json title="mr-data stream series register --file series.json --digest … --json"
{
  "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."
}
```

```json title="mr-data stream probe --document-digest … --registry … --member … --json"
{
  "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."
}
```

```json title="mr-data stream capture --seconds 60 --watch --json"
{
  "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.

```json title="mr-data stream subscribe --series … --json"
{
  "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."
}
```

```json title="mr-data stream subscription show e04a7c93-1b28-4f65-9d07-3c5e8b1a2f64 --json"
{
  "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.

```json title="mr-data stream status 1b7f4e28-6c05-4d39-b82f-9a3e0d5c7146 --json"
{
  "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.

```json title="mr-data stream recordings --document-digest … --json"
{
  "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

| 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

```bash title="from a connector document to a bound recording"
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" --json
```

Then name the venue in the recipe as `{"stream_connector_document_digest": "…"}`, the digest rather than the endpoint.

## See also

- [CLI reference](/docs/reference/cli/): conventions, exit codes, environment variables
- [Recipes and sources](/docs/reference/cli/recipe/): `keys` stores the shaped credential a venue authenticates with
- [Tables](/docs/reference/cli/table/): the tables a subscription's recordings keep fresh
