Skip to content

Hosted Table API

Every Dataset you build and activate in Mostly Right Cloud exposes its Tables over two small HTTPS endpoints: one returns the latest passing Parquet release, the other tells you whether that release is still current. Both use your subscription’s API key and nothing else.

You need two things:

  • An active subscription’s API key, mr_live_…, from app.mostlyright.md. Device credentials issued to the CLI (mr_cli_…) are not accepted here.
  • A Table id. It is minted when a Dataset is activated and is shown on the Table’s page in the app under Use this Table, together with a ready-made curl. The CLI’s activation receipt carries it too, inside current_path; the receipt’s own table_id field is a different, internal identifier and is not what these routes take.

The base URL is https://api.mostlyright.md. The same routes also answer on the app’s own origin.

GET /api/v2/tables/{table_id}/current
x-api-key: mr_live_…
Terminal window
curl -sS -o table.parquet \
-H "x-api-key: $MOSTLYRIGHT_API_KEY" \
"https://api.mostlyright.md/api/v2/tables/$TABLE_ID/current"

A 200 is the Parquet file itself (Content-Type: application/vnd.apache.parquet), served from Mostly Right’s own servers — never a redirect to object storage, so your key is never forwarded anywhere and the URL you hold is never a replayable signed link.

Each successful response carries:

Header Meaning
ETag A strong tag derived from the bytes’ SHA-256. Stable for as long as the release is current.
Content-Digest The SHA-256 of the bytes you received, measured server-side before the first byte was sent, in RFC 9530 form (sha-256=:<base64>:). X-MostlyRight-Content-Digest carries the same digest as sha256:<hex>.
Content-Length Exact. The body is read and verified in full before it is streamed.
X-Request-Id Quote this in any support request; it names the exact event on our side.

Send the ETag you already hold and you get a 304 instead of the bytes when nothing has changed:

Terminal window
curl -sS -o table.parquet -w '%{http_code}\n' \
-H "x-api-key: $MOSTLYRIGHT_API_KEY" \
-H 'If-None-Match: "sha256-…"' \
"https://api.mostlyright.md/api/v2/tables/$TABLE_ID/current"

A 304 is not counted as a download.

Each request resolves the Table’s latest passing release once, then serves exactly that immutable version. If a refresh publishes a newer release while your request is in flight, you still receive the version that was current when you asked; the next request returns the new one.

  • GET and OPTIONS only. HEAD answers 405.
  • Range requests are not supported. The response says so with Accept-Ranges: none, so use a whole-object fetch rather than wget -c.
  • Artifacts are capped at 1 GiB. Larger releases answer 413.
  • Requests are rate-limited per key. A 429 carries Retry-After when the limit can say how long to wait.
GET /api/v2/tables/{table_id}/freshness
x-api-key: mr_live_…
Terminal window
curl -sS -H "x-api-key: $MOSTLYRIGHT_API_KEY" \
"https://api.mostlyright.md/api/v2/tables/$TABLE_ID/freshness"

The answer is JSON and deliberately separates two questions:

{
"schema_version": "mostlyright-cloud-table-freshness.v1",
"request_id": "…",
"dataset_id": "…",
"table_id": "…",
"current": { "available": true, "etag": "\"sha256-…\"" },
"freshness": {
"state": "awaiting_update",
"reason_code": "within_expected_window",
"confidence": "high",
"continuity": "continuous",
"blocking_source": null,
"last_observed_at": "2026-08-22T11:00:00.000Z",
"last_changed_at": "2026-08-22T06:00:00.000Z",
"data_through": "2026-08-22",
"expected_next_earliest": "2026-08-22T18:00:00.000Z",
"expected_next_latest": "2026-08-22T22:00:00.000Z",
"last_good_after_failed_refresh": false,
"sources_incomplete": false,
"unlisted_source_count": 0,
"sources": [ … ]
}
}

(sources is abridged here; each entry carries the same state, reason, confidence, and timestamp fields for one input.)

  • current.available says whether there is a passing release to download. current.etag matches the ETag the download route returns for that release, so you can compare it with a file you already have without fetching it. It is null when a release exists but its coordinate could not be read — a different fact from available: false.
  • freshness.state says whether that release is still the one you would expect given the sources’ publication schedule. A stale Table still serves its last passing bytes; nothing is withdrawn.

freshness.state is a closed vocabulary:

state meaning
fresh Within the expected publication schedule.
awaiting_update An update is due inside the expected window and has not arrived yet.
learning Not enough evidence yet to promise a schedule.
stale The expected window passed without an update.
source_unhealthy Two or more consecutive reads of a source failed. The last good release is still served.
dormant The source is retired or out of service and is not expected to change.
unknown Freshness could not be established. Nothing is claimed.

unknown is the answer whenever the evidence does not decide; the API never reports fresh as a fallback. When sources_incomplete is true, do not derive your own verdict from sources — the input that would have blocked the Dataset may be one of the ones not listed.

The freshness document is never cached (Cache-Control: no-store) and has no ETag of its own: it is a claim about the passage of time, and a cached copy would become wrong in exactly the direction that matters.

Every JSON refusal from either route uses one envelope — { "schema_version", "error", "request_id" } — with the same id in X-Request-Id. The two exceptions have no body to carry it: a 405 for HEAD, and a 503 from a maintenance fence, which is plain text.

Status Download Freshness
200 Parquet bytes. Freshness JSON, whether or not a release exists.
304 Your If-None-Match matches the current release.
401 Missing, malformed, revoked, or expired key, or a key that is not mr_live_. Same.
402 The key is valid but the subscription is not active. Same.
404 Unknown, foreign, or not-yet-activated Table id. These are deliberately indistinguishable. Same.
405 HEAD or any method other than GET/OPTIONS. Same.
409 The Table exists but has no passing release yet. — (reported as current.available: false)
413 The release exceeds 1 GiB.
429 Per-key rate limit. Retry-After when known. Same, always with Retry-After.
502 Upstream integrity failure: the stored bytes did not match the release’s recorded digest, or the release metadata was inconsistent. Nothing was served.
503 The service is temporarily unavailable. Retry after the Retry-After interval. Same.
504 The upstream storage read timed out. Nothing is known to be wrong with the release.

Treat 429, 503, and 504 as transient and retry with backoff, honouring Retry-After when present. A 503 can be a cold start on our side after a quiet period rather than an outage; a second request a few seconds later normally succeeds. 401, 402, 404, 409, and 413 are not retryable without a change on your side.

A 502 means something upstream did not agree with itself — most often the bytes in storage did not match what the release recorded — so nothing was sent. It is rare and is worth reporting with the X-Request-Id.

Your key id and the path you requested are stored only as keyed SHA-256 hashes; raw keys and signed storage URLs are never persisted. Each successful download is recorded against the exact immutable release it served, together with the measured digest of the bytes, so a delivery can always be traced to the release it came from.