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.
Published endpoints
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
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.
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.
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.
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 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
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.
An unknown path and a disabled endpoint return the same 404 body, so the registry cannot be walked one URL at a time.
A session cookie is 401 here. This is a machine surface: keys, bound to paths, owned by a person who can revoke them.
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
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
| endpoint | serves | key 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
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.
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.