Published endpoints

Turn a SQL query into a data 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 the SQL API 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 one use of a released pipeline; the same release can feed a SQL dashboard or run on a schedule.

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 your namespace, a version, and the resource: /api/nyc/v1/rain-share, /api/customers/v1/{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 and nothing else, not the product's own API and 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. A client fixes a request once, not four times.

GET /api/nyc/v1/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, because 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 rather than 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. These are 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. What a published endpoint hands you is a REST API from database tables you already have: the query you released, answered over GET. That quarter is what it 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 dashboards are built the way they are: drafted by the same agent, fed by pinned versions of the same released pipelines, and embedded in your product through your own backend rather than hosted in ours. The released pipeline is the contract; the endpoint and the dashboard are both consumers of it.

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/demo/v1/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 of its own, /api/<your-namespace>/<version>/<path>; 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. The same released pipeline can also feed a dashboard your agent drafts and a person releases, embedded in your product through your own backend (docs/dashboards.md §6.5). Publishing is docs/rest-api.md §19.