Skip to content

Configure the public tables your application can search. Your users enter a query; the server searches that scope and returns one result list. Search strategy is handled by each table’s search engine.

Open a dataset, choose Use → Search API, and select a table. Your workspace key searches every table your workspace owns, whether the dataset is private, shared by link or public, so there is nothing to publish first. For someone else’s dataset, it must be public with row access enabled; list its table IDs with mr-data public tables DATASET_SLUG --json. Replace the illustrative ID below with your table’s ID. The table must have search ready.

Create an mr_use_ key in Settings → Access. Keep it on your server. A workspace mr_live_ key and a CLI device credential are different credentials; do not substitute them in this request.

For a runnable server example, see JavaScript and TypeScript.

POST https://mostlyright.md/api/v2/public/search
x-api-key: mr_use_…
content-type: application/json
{
"query": "vegetarian pasta",
"tables": [
"11111111-1111-4111-8111-111111111111"
],
"limit": 20
}

Use one to five distinct public table IDs with search enabled. query accepts up to 512 characters; an empty query browses the configured tables. limit caps the combined result at 1–100 rows and defaults to 20. Finished searches use settled: true by default.

The backend searches the configured scope with bounded concurrency. It preserves each table’s ranking and interleaves results by rank, breaking ties in the order you configured the tables. Scores from unrelated tables are not treated as a shared relevance scale. Each row carries its source table and immutable table version:

{
"schema_version": "public-search.v1",
"request_id": "33333333-3333-4333-8333-333333333333",
"query": "vegetarian pasta",
"rows": [
{
"table_id": "11111111-1111-4111-8111-111111111111",
"table_version_id": "44444444-4444-4444-8444-444444444444",
"row_id": 42,
"fields": { "title": "Spinach pasta" }
}
],
"tables": [
{
"table_id": "11111111-1111-4111-8111-111111111111",
"status": "ok",
"returned_rows": 1
}
],
"partial": false
}

partial: true means at least one table answered and another was unavailable. Inspect tables to decide whether to display the available rows or ask the user to try again. A valid empty result is successful. If every table is unavailable, the API returns 503. Authentication, permission, allowance and rate-limit refusals remain HTTP errors, including when another table has already answered.

To search one table, send POST /api/v2/public/tables/{tableId}/search with the same key. The body takes query and optionally settled, plan, cursor, like, mode, page_size (1–100, default 20), count and columns. The answer holds rows (row_id, fields), the applied plan and next_cursor. For the next page, send the same query with that plan and with next_cursor as cursor. An empty query browses the table in stored order.

POST https://mostlyright.md/api/v2/public/tables/11111111-1111-4111-8111-111111111111/search
x-api-key: mr_use_…
content-type: application/json
{"query": "vegetarian pasta"}

A public table also answers without a key at POST /api/public/tables/{tableId}/search with the same body and content-type: application/json. Without a key, each address gets 30 searches a minute and 600 a day, and all keyless callers together get 300 a minute and 10,000 a day. The table’s publisher must allow download. A browser page on another site cannot call it, so search from your server. With a key, search shares the key’s query limit of 60 a minute and the workspace’s 5,000 a day.

A table whose search is still being prepared answers 503 search_unavailable with reason preparing and a Retry-After header. After a publisher activates search this can last minutes, and longer on a large table. Retry once after that wait.

The @mostlyright/search package in the Cloud repository provides a server client with no runtime dependencies:

import { createSearchClient } from "@mostlyright/search";
const search = createSearchClient({
apiKey: process.env.MOSTLYRIGHT_API_KEY!,
tables: [
"11111111-1111-4111-8111-111111111111",
],
});
const result = await search.search({ query: "vegetarian pasta", limit: 20 });
for (const row of result.rows) {
console.log(row.table_id, row.fields);
}

Table scope is fixed when the client is created. Each call supplies the query, optional result limit and optional AbortSignal. Model names and search modes are not client options.

The package is available from the Cloud source workspace; registry publication is a separate release. Build and pack it for a local installation:

Terminal window
pnpm --filter @mostlyright/search build
pnpm --filter @mostlyright/search pack

Install the resulting tarball in your server application. The HTTP endpoint can also be called directly with native fetch.

Keep the key and configured table list on your server. Add a same-origin route protected by your app’s authentication and per-user request limits. That route accepts a query, calls the server client, and returns its result. It must take table IDs from server configuration, rather than from the browser request.

The browser calls your route:

const response = await fetch("/api/search", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ query: input.value }),
signal: controller.signal,
});
if (!response.ok) throw new Error("Search could not be completed.");
const result = await response.json();

The server client refuses browser execution. Never place an mr_use_ key in a browser bundle or a public environment variable.

Every table search uses the existing per-table limits and usage accounting. One unified call can run up to five table searches. Each source can return up to limit rows before the combined result is trimmed, so accounting can include rows that do not appear in the combined list. Completed table requests remain counted if another table subsequently causes the overall call to be refused.

The client exposes typed SearchError failures, including HTTP status, request ID and retry delay when supplied. It does not retry automatically. Forward an AbortSignal to cancel queued work and propagate cancellation to active searches. Cancellation does not reverse work already accepted or usage already recorded. The backend also bounds the overall request deadline.

This endpoint returns the first combined results. For table-specific pagination and additional controls, use the existing table search endpoint in the Public Dataset API. Browser search tokens and local typing indexes are separate SDK work.