For agents
Use six curl requests to find a dataset, inspect it, query it and cite the result.
Every step here is one curl. The first four need no key. A bounded query of up to 100 rows a page needs none either. A key lifts that to 10,000 rows a page, and to the whole table by paging.
Read the docs as an agent
Section titled “Read the docs as an agent”Fetch /llms.txt for the short index, or /llms-full.txt for the same index plus every public dataset, its tables, and the API paths that read them. The OpenAPI document is the complete request and response contract.
/llms.txt names every documentation page with one line saying what it covers. Fetch the page itself when you need the field names.
| What you are doing | Page to fetch |
|---|---|
| Writing or fixing a recipe document | /docs/reference/recipe/ |
| Choosing a source kind or a connector | /docs/reference/sources/ |
| Picking the reader for a format | /docs/reference/readers/ |
| Writing a transform step | /docs/reference/transforms/ |
| Declaring a check | /docs/reference/checks/ |
| Naming a unit | /docs/reference/units/ |
| Reading a run state or a failure code | /docs/reference/run-states-and-errors/ |
| Finding a ceiling before a run hits it | /docs/reference/limits/ |
| Starting from a recipe that already ran | /docs/recipes/ |
| Running a command | /docs/reference/cli/ |
pip install mostlyright-data puts the mr-data command on the path, and mr-data login signs one machine in. The first mr-data command of any kind also writes the mr-data-build agent skill to ~/.claude/skills/mr-data-build, or under ~/.codex/skills for Codex. It leaves an edited copy alone, and MOSTLYRIGHT_SKILL_AUTOINSTALL=0 stops it writing at all. Install and sign in is the whole setup, and build your first dataset runs one end to end.
Building over MCP uses a different skill document. /.well-known/agent-skills/index.json lists it, and the file is at /.well-known/agent-skills/mostlyright-build/SKILL.md.
The examples use https://api.mostlyright.md. The same routes answer on the app’s own origin.
1. Search the catalog
Section titled “1. Search the catalog”curl -sS "https://api.mostlyright.md/api/v2/public/datasets?q=weather&limit=20"Anonymous. Each match returns dataset identity, publisher (handle, display name, avatar) and topics. It also returns published and updated dates, the cover image, all-time views and likes, and a canonical page URL. A next_cursor comes back when there is another page. Pass cursor= to continue. limit is 20 by default and 100 at most.
2. Read a dataset
Section titled “2. Read a dataset”curl -sS "https://api.mostlyright.md/api/v2/public/datasets/$DATASET_SLUG"Anonymous. It returns the dataset’s tables. Each table carries the id every later step needs, the current version_id and its licence, and says if preview and download are enabled for it.
3. Read a table’s schema
Section titled “3. Read a table’s schema”curl -sS "https://api.mostlyright.md/api/v2/public/tables/$TABLE_ID/schema"Anonymous. It returns the column names and types to write a query against, plus the table’s capabilities. Read the schema before you compose a query. The API rejects unknown columns before it scans a table, and a guess costs you a request.
4. Preview the rows
Section titled “4. Preview the rows”curl -sS "https://api.mostlyright.md/api/v2/public/tables/$TABLE_ID/preview"Anonymous. It returns a materialized sample of at most 100 rows, from the table’s current immutable version. The response is cached, so this is the cheapest way to see what the data looks like.
rows is an array of row arrays positionally aligned with columns. The response reports row_count_in_preview against total_row_count, and sets preview_truncated when what you received is only a prefix.
Preview is a fixed sample and takes no filters.
Without a key, POST /api/public/tables/{table_id}/query takes the same body as the keyed query in step 5. It answers at most 100 rows a page, 30 queries a minute per address. It lets a visitor sort and filter a table. A key is what reads the whole table.
5. Query with a key
Section titled “5. Query with a key”Everything past this point reads real data on behalf of a workspace, so it needs a key. Sign in and create a personal mr_use_… key under Settings → Access. The first keyed request over a dataset connects it to your workspace, with no separate step. See Use a public dataset for what a key can and cannot read.
curl -sS \ -H "x-api-key: $MOSTLYRIGHT_API_KEY" \ -H "content-type: application/json" \ -d '{ "columns": ["city", "date", "temperature"], "filters": [{ "column": "date", "operator": "gte", "value": "2026-08-01" }], "order_by": [{ "column": "temperature", "direction": "desc" }], "limit": 25 }' \ "https://api.mostlyright.md/api/v2/public/tables/$TABLE_ID/query"The request body is a closed query grammar, not SQL. It accepts columns, filters, order_by, group_by, aggregates and limit, plus the paging and version-pin fields the reference lists, and rejects any other field. No endpoint runs a SQL string.
The response carries the rows and the columns with their types. It names the exact version_id and content_digest the result came from. An execution block reports returned_rows, scanned_bytes, elapsed_ms and truncated.
group_by beside aggregates answers one row a group, ordered by a group column or an aggregate alias. limit counts groups, and a grouped result has no next page.
curl -sS \ -H "x-api-key: $MOSTLYRIGHT_API_KEY" \ -H "content-type: application/json" \ -d '{ "columns": ["city", "temperature"], "group_by": ["city"], "aggregates": [{ "function": "avg", "column": "temperature", "as": "mean_temperature" }], "order_by": [{ "column": "mean_temperature", "direction": "desc" }], "limit": 25 }' \ "https://api.mostlyright.md/api/v2/public/tables/$TABLE_ID/query"mr_use_… is the only key type these two endpoints accept. The legacy SDK key mr_live_… and the device credential mr_cli_… get a 401.
6. Download the current Parquet version
Section titled “6. Download the current Parquet version”curl -sS -o table.parquet \ -H "x-api-key: $MOSTLYRIGHT_API_KEY" \ "https://api.mostlyright.md/api/v2/public/tables/$TABLE_ID/current"The body is application/vnd.apache.parquet, the complete current snapshot, with a strong ETag and a SHA-256 Content-Digest. Save the ETag and send it as If-None-Match next time. A 304 means nothing changed and you download nothing.
Query the file locally once you have it:
import duckdb
rows = duckdb.sql(""" select city, avg(temperature) as mean_temperature from read_parquet('table.parquet') group by city order by mean_temperature desc""").fetchall()Limits
Section titled “Limits”| Limit | With a key | Without a key |
|---|---|---|
Rows a page from query |
25 by default, 10,000 maximum | 25 by default, 100 maximum |
Page depth (cursor or offset) |
100,000,000 rows | 1,000,000 rows |
| Result size | 8 MiB of JSON | 64 KiB of JSON |
| Queries | 60 a minute, 5,000 a day per key | 30 a minute, 600 a day per address |
| Execution time | 15 seconds | 15 seconds |
| Columns, filters, sort keys | 64, 8, 2 | 64, 8, 2 |
| Group keys, aggregates | 4, 4 | 4, 4 |
Rows returned by preview |
100 maximum | 100 maximum |
A key reads a whole table through query by following next_cursor. Every page carries one. Send it back as cursor with the same columns, filters and order_by, and stop when it comes back null. The cursor pins the immutable version the first page came from, so a refresh landing mid-walk never mixes two versions. offset still works if you would rather compute the windows yourself. For one request that returns everything, download Current instead, verify the digest, and scan the Parquet file locally with DuckDB, Polars, Pandas, or Arrow.
body='{"columns": ["city", "date", "temperature"], "limit": 10000}'while [ -n "$body" ]; do page=$(curl -sS -H "x-api-key: $MOSTLYRIGHT_API_KEY" -H "content-type: application/json" \ -d "$body" "https://api.mostlyright.md/api/v2/public/tables/$TABLE_ID/query") echo "$page" | jq -c '.rows[]' >> rows.ndjson next=$(echo "$page" | jq -r '.next_cursor // empty') body=${next:+"{\"columns\": [\"city\", \"date\", \"temperature\"], \"limit\": 10000, \"cursor\": \"$next\"}"}doneA 429 means you have reached a rate limit. Respect Retry-After. A 413 means the artifact or result is too large, so narrow the query or switch to Current. See Public Dataset API for the complete error table.
Cite what you used
Section titled “Cite what you used”A result is only reproducible if the reader can fetch the same bytes. Cite five things:
- the dataset name and its canonical page URL
- the table
- the immutable
version_idthe result came from - the
content_digest - the time you ran the query
A query response hands you table.version_id, table.content_digest and citation.dataset_url directly. A Current download hands you the ETag and Content-Digest headers. The anonymous preview is citable too. It carries version.id, version.content_digest, citation.dataset_url and citation.table_url, so you can cite rows you have seen without ever holding a key.
content_digest is null on versions published before all-rows analytics existed. Cite the version id alone in that case, rather than implying a digest. A version_id printed in a listing is the version that was current when the listing was generated, so cite the one your own response returned.
Or connect over MCP
Section titled “Or connect over MCP”An AI client rather than a script points at https://mostlyright.md/api/mcp and skips the curl ladder. See connect an AI tool.
Steps 1 to 4 have MCP equivalents that need no credential at all: search_datasets, get_dataset, get_table_schema and sample_rows. Clients that speak the two-tool connector contract also get search and fetch. query_table is the same gate step 5 is. It takes either an OAuth connection your client negotiates in a browser, or the same mr_use_… key sent as Authorization: Bearer. Bulk Parquet stays on the HTTP API, and get_download_instructions hands you the exact request.
Or build a dataset
Section titled “Or build a dataset”Steps 1 to 6 read data somebody else published. The MCP server also builds, on a paid workspace, over an OAuth connection carrying the datasets:build scope. An agent writes a recipe document and registers it with register_recipe. It runs the recipe with start_run, follows it with run_events, and checks the rows with query_run. promote_table publishes the table once a person approves. A person pastes source credentials into the dashboard and the recipe names them, so nothing passes a secret through the protocol.
/.well-known/agent-skills/mostlyright-build/SKILL.md writes the loop out in full, and the server serves the same text as its mostlyright://build-guide resource. See connect an AI tool for the tool list and what each tier costs.