Public API
Access public Namespace data and anonymous file conversion using the /v1/public endpoints.
Overview
The public API lets anyone browse ordinary catalog nodes and read bounded Table rows when those nodes have effective Everyone viewer access. No authentication is required. Namespaces can nest to arbitrary depth; Tables and Views are leaves in that same catalog tree.
Public access uses the same node ancestry and grant resolver as authenticated access. An Everyone viewer allow on a Namespace publishes readable descendants unless an applicable deny vetoes access. There is no separate visibility setting or publication hierarchy.
Making data public
Create an ordinary Everyone viewer grant on the Namespace, Table, or View node to publish it. Only a human with effective Admin on the target node can manage that grant. Editors and machine credentials cannot publish data.
curl -X POST https://rw-us-east1.api.loamdata.com/v1/workspaces/rw-us-east1-ws-0195a3c47d3e7ab18c4f123456789abc/grants \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"effect": "allow",
"subjectPrincipalId": "018f5f65-4a8c-7c2e-a4d9-7c7bf7e43100",
"resourceId": "rw-us-east1-node-0195a3c47d3e7ab18c4f123456789abd",
"role": "viewer"
}'To unpublish the direct rule, delete that ordinary grant by grant ID. An inherited Everyone allow or deny still applies through the node's current Namespace ancestry. A deeper Everyone allow cannot reopen a subtree denied higher in that public lane. Everyone Editor or Admin allows are rejected. Everyone denies do not cancel authenticated user or team grants.
Search discoverability is separate node metadata. It only controls eligibility for public search after effective Everyoneviewer access has been established; it never grants read access or publishes descendants by itself.
Public endpoints
Public node reads use a stable regional node ID, which resolves the owning Workspace locally. Do not also send a workspace slug, Workspace ID, or Namespace path as a second selector. No Authorizationheader is needed.
GET /v1/public/namespaces
Search or enumerate Namespaces that are both explicitly discoverable and effectively readable by Everyone. Inherited descendants are not automatically separate discovery results.
curl "https://rw-us-east1.api.loamdata.com/v1/public/namespaces?q=retail&limit=20&offset=0"GET /v1/public/nodes/{nodePublicId}
Read an effectively public Namespace, Table, or View by stable node ID. The response derives its current path and Namespace ancestors from the catalog tree.
curl https://rw-us-east1.api.loamdata.com/v1/public/nodes/rw-us-east1-node-0195a3c47d3e7ab18c4f123456789abdGET /v1/public/nodes/{namespaceNodePublicId}/children
List the Namespace's directly contained nodes. The response omits any child without effective anonymous readability.
curl https://rw-us-east1.api.loamdata.com/v1/public/nodes/rw-us-east1-node-0195a3c47d3e7ab18c4f123456789abd/childrenGET /v1/public/nodes/{tableNodePublicId}/schema
Get the schema of an effectively public Table by stable node ID.
curl https://rw-us-east1.api.loamdata.com/v1/public/nodes/rw-us-east1-node-0195a3c47d3e7ab18c4f123456789abe/schemaGET /v1/public/nodes/{tableNodePublicId}/rows
Read a bounded page of rows from an effectively public Table.
curl "https://rw-us-east1.api.loamdata.com/v1/public/nodes/rw-us-east1-node-0195a3c47d3e7ab18c4f123456789abe/rows?rowOffset=0&rowLimit=100&columnOffset=0&columnLimit=64"Anonymous file conversion
Loam also exposes no-auth CSV and Parquet conversion endpoints. The source and destination formats are path parameters, not query parameters.
POST /v1/public/file-conversions/{from}/{to}
Send raw text/csv or application/vnd.apache.parquet bytes in the request body. The response streams the converted file with fixed download headers and Cache-Control: no-store.
curl -X POST https://rw-us-east1.api.loamdata.com/v1/public/file-conversions/csv/parquet \
-H "Content-Type: text/csv" \
--data-binary @input.csv \
-o converted.parquetTo convert by URL, first use the upload helper below, upload the source file to the returned uploadUrl, then submit the returned sourceUrl. The conversion endpoint only accepts Loam-issued signed temporary URLs under tmp/YYYY-MM-DD/{uuid-v7}/; arbitrary remote URLs and durable workspace object paths are rejected before fetch.
curl -X POST https://rw-us-east1.api.loamdata.com/v1/public/file-conversions/parquet/csv \
-H "Content-Type: application/json" \
-d '{"url": "SIGNED_SOURCE_URL_FROM_UPLOAD_HELPER"}' \
-o converted.csvPOST /v1/public/file-conversions/{from}/{to}/uploads
Use this helper when browsers or large files should upload directly to temporary object storage. The request declares a positive contentLength; Loam returns a short-lived PUT uploadUrl, a signed GET sourceUrl, and expiresAt.
UPLOAD=$(curl -s -X POST https://rw-us-east1.api.loamdata.com/v1/public/file-conversions/csv/parquet/uploads \
-H "Content-Type: application/json" \
-d '{"contentLength": 12345}')
curl -X PUT "$(echo "$UPLOAD" | jq -r .uploadUrl)" \
-H "Content-Type: text/csv" \
--data-binary @input.csv
curl -X POST https://rw-us-east1.api.loamdata.com/v1/public/file-conversions/csv/parquet \
-H "Content-Type: application/json" \
-d "$(jq -nc --arg url "$(echo "$UPLOAD" | jq -r .sourceUrl)" '{url: $url}')" \
-o converted.parquetFile conversion has no application-level file-size cap and no concurrent-conversion slot cap. It still enforces a 1,024-column schema limit, a rolling 30 conversions per minute per engine, request deadlines, and available service capacity. Inputs and outputs stream through temporary object storage and incremental Arrow/Parquet batches so large conversions do not require the whole file in memory.
Temporary objects live in the existing durable bucket under tmp/YYYY-MM-DD/{uuid-v7}/, which is separate from workspaces/ table data. Uploaded source objects are deleted after an admitted URL conversion, and converted output objects are deleted after the response stream finishes or after disconnect cleanup.
Conversion errors use stable HTTP status codes with redacted bodies: 400 for unsupported formats, malformed files, arbitrary URLs, or expired/forged temporary URLs; 413 when the schema exceeds the column limit; 429 when the rolling admission limit is reached; 503 when temporary storage is unavailable or the conversion deadline is exceeded; and 500 for unexpected internal failures.
Example: embed public data
You can use the public API to embed live data on a website or dashboard without exposing any credentials:
const TABLE_ID = "rw-us-east1-node-0195a3c47d3e7ab18c4f123456789abe";
const API_URL = "https://rw-us-east1.api.loamdata.com";
async function fetchPublicData() {
const url = new URL(`/v1/public/nodes/${TABLE_ID}/rows`, API_URL);
url.searchParams.set("rowOffset", "0");
url.searchParams.set("rowLimit", "100");
url.searchParams.set("columnOffset", "0");
url.searchParams.set("columnLimit", "64");
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Public row read failed: ${res.status}`);
}
return res.json();
}Limitations
- Anonymous catalog access is read-only and ID-addressed. It does not expose a SQL or pivot endpoint.
- Anonymous reads require an effective
Everyoneviewer allow on the node or an ancestor Namespace. Any applicable Everyone deny vetoes that public access. - Discoverability controls search eligibility only. It does not create a grant or make an otherwise private node readable.
- Public node, child, schema, and row URLs use stable node IDs, so they continue addressing the same nodes after a Namespace rename or move.
Last updated 2026-08-22