Skip to content

Recipes and sources

Find a source with catalog, question it with probe, store its credential with keys, then register the whole recipe document with recipe.

Terminal window
mr-data recipe FILE [--no-activity] [--json]
mr-data recipe show RECIPE_ID [--json]
mr-data recipe readiness [--json]

mr-data recipe registers one whole recipe document and prints the identifiers the server derived: the recipe, its digest, the dataset, the table and the sources. Identical bytes converge on one recipe by digest, so registering the same file twice is idempotent. A changed document is a revision against the same dataset and table.

Registration is the slowest step a person watches, so the command tells the dataset page it is drafting before it sends. Write the document first (recipe reference), register it, then start a run with the identifier and digest this prints.

The positional is a path to a JSON file, or the word show followed by a recipe identifier. recipe show on its own refuses by name rather than registering a file called show. Name such a file with a directory in front of it.

Flag Argument Required Meaning Default
FILE path or show yes The recipe document to register, or the word show. none
RECIPE_ID id with show The recipe to read back. none
--no-activity none no Do not tell the dataset page this command is drafting. off
--json none no One JSON object instead of human lines. off

This client checks the cheap half before it sends. The file parses as strict JSON, it is one object and it is at most 1 MiB. It carries dataset, question, table, sources, transform, checks, units and timezone. Studio validates the whole document against recipe.schema.json.

mr-data recipe recipe.json --json
{
"schema_version": "mostlyright-thin-client-recipe.v1",
"status": "recipe_registered",
"lane": "hosted",
"recipe_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"recipe_digest": "9f3c2b…",
"dataset_id": "d31017e0",
"table_id": "3f8c2d10-6a4b-4c8e-9d1f-5b7a0c2e4d68",
"source_ids": ["2c7d5b18-9e04-4a63-8b1d-6f3a9c0e5d72", "8b1f0a63-4d29-4e75-9c08-3a6d2b7f1e40"],
"columns_without_description": 3,
"warnings": { "warning 1": "…", "warning 2": "…" },
"idempotent_by": "recipe_digest"
}

Values are illustrative. A clean registration carries no columns_without_description and no warnings. recipe show answers schema_version: "mostlyright-thin-client-recipe-read.v1", status: "recipe_reported", the same five identifiers, and document carrying the registered document verbatim.

Errors:

Code When
THIN_RECIPE_DOCUMENT_UNREADABLE The file could not be read, or runs over 1 MiB.
THIN_RECIPE_DOCUMENT_INVALID Not strict JSON, not one object, or missing a required member. The refusal names the path, the reason and the member rather than handing back a 422. A recipe document carries integers, strings, booleans, null, objects and lists, and never a fractional number.
THIN_REQUEST_INVALID recipe show with no identifier, or a second positional after a file.
THIN_RESPONSE_INVALID Studio registered a recipe without one of the five identifiers, or without any source identifiers.
THIN_STUDIO_* Studio’s own schema validation and contract refusals, carried through under their own codes.
register, then run
REG=$(mr-data recipe recipe.json --json)
mr-data run --recipe "$(jq -r .recipe_id <<<"$REG")" \
--digest "$(jq -r .recipe_digest <<<"$REG")" \
--sample --max-rows 500 --json
Terminal window
mr-data recipe readiness [--json]

Read every recipe revision in the active workspace and classify whether its current table can refresh incrementally. This is an inventory read: it starts no run and changes no recipe. Each row names the immutable recipe revision, whether it is current, the table’s live state, refresh_ready, every source’s classification (closed, window, collection, recorded, or resync_only), and the names blocking incremental refresh.

Use it before a broad recipe migration. A refresh_ready: false row is not permission to retry a whole source on the schedule; correct its recipe or use the explicit table resync operation when a full reread is intentional.

Terminal window
mr-data probe SOURCE QUESTION --dataset DATASET
[--kind {source_inspect,sample_rows,profile_columns,evaluate_expression}]
[--request JSON | --request-file FILE] [--json]

mr-data probe asks one source one question through a warm worker, before a recipe commits to it. It opens or reuses a session and closes it, so there is no session to name. It closes the session it opened even when the probe is refused.

Use it to learn a source’s shape, its columns and its first rows, while the table is still undecided. source_inspect, the default, reports the shape and no cell values.

Two limits. The command reaches the frozen v3 session routes, and a deployment that has switched them off refuses it. It also takes a question identifier, which no v4 registration receipt returns. On a hosted-only workspace, register the recipe, take an unwindowed sample under --max-rows, then run peek, query and receipt. Probe a source walks through that.

Flag Argument Required Meaning Default
SOURCE id yes The source to ask about, by its identifier. none
QUESTION id yes The question being explored, by its identifier. A session is bound to one. none
--dataset DATASET yes The dataset the source and the question belong to. none
--kind one of four no What to ask. source_inspect reports shape only. sample_rows needs a limit in the request body. profile_columns and evaluate_expression take their own bodies. source_inspect
--request JSON object no The rest of the request body as one JSON object. The source is filled in from SOURCE. none
--request-file FILE no The same body read from a JSON file instead of the command line. none
--json none no One JSON object instead of human lines. off
mr-data probe SRC QST --dataset D --json
{
"schema_version": "mostlyright-thin-client-probe.v1",
"status": "probe_answered",
"lane": "hosted",
"source_id": "2c7d5b18-9e04-4a63-8b1d-6f3a9c0e5d72",
"question_id": "6a0d3e91-7c58-4b2f-8d16-4e9b0c3a7f52",
"dataset_id": "d31017e0",
"probe_kind": "source_inspect",
"answer": {},
"session": { "reused": true, "closed": true },
"not_evidence": "…"
}

Values are illustrative. status is probe_answered when the worker answered, and probe_settled when it settled without an answer, having failed, expired or been cancelled. On probe_settled, answer is null, failure_code carries the worker’s code when there is one, and the command exits 2.

Errors: THIN_RESEARCH_SESSION_ENDED and THIN_RESEARCH_SOURCE_FORBIDDEN. THIN_SESSION_BINDING when Studio answered about a different session, probe or session run than the one asked about, which says nothing about the session’s own state. THIN_REQUEST_INVALID for a --request that is not one JSON object, or a --kind that needs a body and got none. A workspace already at its bound of live research sessions refuses with retry_after_seconds on the refusal.

Terminal window
mr-data probe "$SOURCE_ID" "$QUESTION_ID" --dataset "$DATASET_ID" --kind sample_rows \
--request '{"limit": 20}' --json
Terminal window
mr-data catalog search QUESTION [--format FORMAT] [--limit N] [--json]

mr-data catalog search asks the public-source catalogue one question and reads back the entries it ranks against it, best first. Every ranked entry carries the disposition its own facts earned, an entry the catalogue could not vouch for included.

It fetches nothing and registers no source. The answer is discovery evidence over one provider’s partial sweep, and it is never exhaustive. Search elsewhere too.

Flag Argument Required Meaning Default
search none yes The one verb. It asks the catalogue and ranks what it holds. none
QUESTION text yes What the data has to be about, in plain language. The grain, the place and the period, not a filename. none
--format csv, json, … no The one data format this question requires. It states the requirement and does not filter. An entry that does not declare the format still comes back, ranked, with disposition: "refused" and filters_match: false. The catalogue accepts one format per question. none. No entry is rejected for its format alone, and most entries declare none
--limit 1 to 25 no How many entries to rank. 10
--json none no One JSON object instead of human lines. off
mr-data catalog search '…' --json
{
"schema_version": "mostlyright-thin-client-v4-catalog-search.v1",
"status": "catalog_searched",
"lane": "hosted",
"question": "daily maximum temperature by US city since 2020",
"incomplete_code": null,
"requirement": { "data_format": "csv" },
"generation": { "store_digest": "9f3c…", "entry_count": 262144 },
"work": { "ranges_scanned": 12, "ranges_pruned": 40, "candidates_examined": 900, "member_bytes_read": 811000 },
"candidates": {
"source 1": {
"rank": 1,
"title": "…",
"publisher": "…",
"description": "…",
"spatial_scope": "…",
"data_formats": ["csv"],
"formats": "csv",
"access_kind": "…",
"authentication_required": false,
"rights": "…",
"disposition": "admitted",
"filters_match": true,
"provider_id": "…",
"provider_record_id": "…",
"provider_record_url": "https://…",
"entry_id": "…",
"entry_coordinate": "…",
"entry_digest": "…"
}
},
"next_step": "Each entry is discovery evidence, not a source: …"
}

Values are illustrative. formats is the one member this client derives. It is "unknown" when the catalogue holds no format for the entry, and "none declared" when the entry declares an empty list. data_formats beside it is the contract’s own value, untouched.

status is catalog_searched, catalog_no_admitted_source, catalog_search_incomplete or catalog_unavailable. On catalog_search_incomplete, incomplete_code says why the search stopped early. catalog_unavailable is not an error. That deployment has no catalogue to search, the payload includes refusal: {"code": "THIN_CATALOG_UNAVAILABLE", "detail": "…"} with every other block null, and the command exits 0.

Errors: THIN_RESPONSE_INVALID when Studio answers without the question, generation or work blocks that make a ranking citable, or with an unreadable entry. THIN_REQUEST_INVALID for a --limit outside 1 to 25, or a second --format.

Terminal window
mr-data catalog search "hourly electricity demand by US balancing authority" --format csv --limit 5 --json
Terminal window
mr-data connections [--dataset DATASET] [--json]

mr-data connections lists the saved connections this workspace holds, so a recipe can name one. It returns the connection identifiers only, no credential, and it changes no grant.

A recipe references a connection as source.connection, using connection_id and version_digest. The dataset needs an explicit grant, so set dataset.id in the recipe.

Flag Argument Required Meaning Default
--dataset DATASET no Only show connections granted to this dataset ID. every connection this workspace holds
--json none no One JSON object instead of human lines. off
mr-data connections --json
{
"schema_version": "mostlyright-thin-connections.v1",
"connections": [
{
"connection_id": "0f5b8c27-3a19-4d64-9e73-2c8d1a6f0b45",
"name": "kalshi-prod",
"display_name": "Kalshi (production)",
"status": "active",
"version_digest": "9f3c…",
"origin": "https://api.elections.kalshi.com",
"source_parameters": {}
}
],
"truncated": false,
"next_step": "Use connection_id and version_digest in source.connection. The dataset must have an explicit grant; set dataset.id in the recipe."
}

Values are illustrative. A row leaves out a member the record does not carry rather than writing null.

Errors: THIN_RESPONSE_INVALID when Studio returns no connection list. THIN_AUTHORIZATION_DENIED, THIN_NOT_FOUND.

Terminal window
mr-data connections --dataset "$DATASET_ID" --json | jq '.connections[] | {connection_id, version_digest}'
Terminal window
mr-data keys set NAME (--value-file FILE | --part NAME=VALUE ) [--json]
mr-data keys list [--json]
mr-data keys rm NAME [--json]

mr-data keys stores a source credential by name so a recipe can reference it as {"secret_name": "…"}. It reads the value from a file or from standard input, never from the command line. Argv shows up in ps, in shell history and in CI logs.

The recipe carries the name. The worker redeems a one-time handle for the value when it fetches the source. Values never appear in recipes, receipts, events or logs.

Flag Argument Required Meaning Default
set|list|rm word yes set stores a credential under NAME, list names the ones this workspace holds, rm withdraws one. none
NAME name set, rm Lowercase letters, digits, dashes and underscores, starting with a letter, at most 63 characters. none
--value-file FILE with set The file holding the value. - reads standard input. One trailing newline is removed, because a credential stored with the one echo added fails at the source and nothing says why. At most 65536 bytes. none
--part NAME=VALUE with set One named part of a shaped credential, which is what a stream venue authenticates with. NAME=@PATH reads that part off disk, which is how a signing key arrives. Repeatable, up to 4 parts. Cannot be combined with --value-file. none
--json none no One JSON object instead of human lines. off

The parts named decide the shape, and the four sets are disjoint:

Shape Parts
token token
key_pair key_id, secret
key_pair_passphrase key_id, secret, passphrase
key_id_pem key_id, private_key_pem
mr-data keys set NAME --value-file - --json
{
"schema_version": "mostlyright-thin-client-v4-secret.v1",
"status": "secret_stored",
"lane": "hosted",
"secret": {
"name": "kalshi-api",
"status": "active",
"created_at": "2026-09-12T09:00:00Z",
"rotated_at": null,
"rotation_generation": 1
},
"rotated": false,
"trailing_newline_removed": true,
"parts": [],
"recipe_reference": { "secret_name": "kalshi-api" },
"note": "The value is stored and was not printed. …"
}

Values are illustrative. parts holds the part names on a shaped credential and never a value. rotated says this was a rotation rather than a first enrolment.

mr-data keys list --json
{
"schema_version": "mostlyright-thin-client-v4-secrets.v1",
"status": "secrets_listed",
"lane": "hosted",
"count": 2,
"secrets": {
"credential 1": { "name": "kalshi-api", "status": "active", "created_at": "2026-08-…", "rotated_at": null }
}
}
mr-data keys rm NAME --json
{
"schema_version": "mostlyright-thin-client-v4-secret-withdrawn.v1",
"status": "secret_withdrawn",
"lane": "hosted",
"secret": { "name": "kalshi-api", "status": "revoked", "created_at": "…", "rotated_at": "…", "rotation_generation": 2 },
"note": "Nothing resolves kalshi-api any more: no future run redeems a handle for it and no recipe may reference it. Runs that already fetched with it are unchanged."
}

Errors:

Code When
THIN_REQUEST_INVALID A name outside the grammar. set with neither --value-file nor --part, or with both. A --part that is not NAME=VALUE. More than four parts. A set of part names matching none of the four shapes. --part NAME=@PATH naming a file that could not be opened.
THIN_SECRET_MATERIAL_INVALID The value, or one part, runs over 65536 bytes. The refusal names a size and never the value.
THIN_STUDIO_* The vault’s own refusals, carried through under their own codes.
store a token without it touching argv
printf '%s' "$KALSHI_TOKEN" | mr-data keys set kalshi-api --value-file - --json
store a signing key pair for a stream venue
mr-data keys set kalshi-ws \
--part key_id=1d2c3b4a \
--part private_key_pem=@./kalshi.pem \
--json
  • CLI reference: conventions, exit codes, environment variables
  • Runs: starting a run against what recipe registered
  • Streams: the venues a shaped credential authenticates against