Published endpoints

Turn a SQL query into an API — versioned, parameterised, scoped by key

Building an API over a dataset is usually a second project: a service, a deployment, auth, pagination, on-call. Here it is one step after a release. A released pipeline is published under a path, a GET runs it with bound parameters and returns JSON, and a key scoped to that path is the whole access story. From data to an API in a day is the shipped half of the sentence; the dashboards you embed are planned.

The mechanism

A release, a path, a key

  1. Release the pipeline

    Only a released, side-effect-free pipeline can be published — a version a person locked, with no node that writes anywhere. The endpoint serves whatever version its pipeline's pointer names, so switching the served version is a lifecycle verb, not a redeploy.

  2. Publish it under a path

    A path is one to ten segments, literal or {variable}: /api/x/nyc/rain-share, /api/x/customers/{id}/orders. Path variables bind to the pipeline's declared parameters by name; the query string supplies the rest. Ambiguous patterns are refused at publish time, not resolved at call time.

  3. Bind a key to the path

    An API key is bound to a path prefix. A caller holding it can reach that subtree of /api/x and nothing else — not the product's own API, not another workspace's endpoints. Give a partner one key per contract and the contract is enforced by the router.

  4. Call it

    A GET with the key in DP-API-Key. Validation reports every defect at once — an unknown parameter, a repeated one, a missing required one, a wrong type — because a client fixes a request once, not four times.

GET /api/x/nyc/rain-share?quarter=2024-Q3
DP-API-Key: dpk_<id>.<secret>

200 OK
{
  "execution_id": "…",
  "schema":  [ { "name": "borough", "type": "STRING" },
               { "name": "rideshare_rain_lift", "type": "BIGDECIMAL", "precision": 18, "scale": 2 } ],
  "rows":    [ ["Manhattan", "1.18"], ["Brooklyn", "1.07"] ],
  "row_count": 5, "total_rows": 5, "has_more": false,
  "result_url": "…", "expires_at": "…", "ttl_seconds": 300
}

What the response is

The result, paged, with a cursor — never a cached one

The body is the same data_ready payload the product's own execution stream produces: a schema with typed columns, the first page of rows, the total, and a cursor URL for the rest. Each call is a fresh execution of the released version, run in-process as the endpoint's workspace, audited with the key, the endpoint and the execution id. The response carries Cache-Control: no-store on purpose — a result belongs to one execution, and a shared cache that held one would hand a second caller another caller's rows.

When an endpoint's own timeout elapses, the execution is not killed and the client is not lied to: the response is 202 with the execution id, a status URL and the result URL, and the rows are there when the run finishes. A 504 would say the upstream failed; it did not.

What is returned is the pipeline's caller node — declared, not guessed from the graph's shape — so a pipeline that also writes to a table, or stages six intermediate results, still returns exactly one declared result.

What it is not

A read surface, by construction

GET only

Every other method is 405 with Allow: GET. A published pipeline cannot have a write-shaped node, and the check is re-run at serve time — a pipeline that gained a write after publishing answers 503, not a write.

Not enumerable

An unknown path and a disabled endpoint return the same 404 body, so the registry cannot be walked one URL at a time.

Never a browser session

A session cookie is 401 here. This is a machine surface: keys, bound to paths, owned by a person who can revoke them.

Rate- and concurrency-limited

429 with Retry-After when a caller exceeds its budget or the instance's execution slots — the same limits the product's own runs live under.

Why it matters

The API was always the expensive part

Teams that already have the data and already have the chart still spend a quarter getting the number into their own product, because “expose this dataset as an API” means a service, a schema, an auth story, a deployment pipeline and someone to wake up when it fails. That quarter is what a published endpoint removes. The agent authors the pipeline against your databases and iterates it until the numbers are right; you review and release; the endpoint exists. No service was written. Nothing was deployed. The on-call is the server you were already running.

It is also why the roadmap's dashboards are built the way they are — created by the same agent, fed by the same released pipelines and endpoints, embedded in your product rather than hosted in ours. The endpoint is the contract; the dashboard is a consumer of it, like any other.

Where you manage them

The API section: endpoints, keys, the MCP server — one screen

Published endpoints with the version each one serves, the keys bound to each path, and the MCP connection details.
workspace: demo · API
endpointserveskey reach
GET /api/x/demo/top-company-by-borough?anchor_date=2025-01-01 demo/top_company_by_borough · v1 /demo/**

from the demo workspace — released by a human, served as this version forever

Asked before

Published endpoints, in two questions

What does “published endpoint” mean?

A released pipeline gets a URL under /api/x/…; a GET runs it with the query string bound to its declared parameters and returns the rows as JSON. Keys are bound to a path prefix, so a partner can be given one endpoint and nothing else. The serving contract is docs/rest-api.md §19.

From data to an API in a day — is that real?

For the API half, today: an agent authors the pipeline against your databases, runs it, fixes what fails, you review and release, and the endpoint exists — no deployment, no service to write, no gateway to configure. Dashboards your agent creates and you embed in your own product are a planned roadmap item; the roadmap page says so. Publishing is docs/rest-api.md §19.