Authoring a Page via the API
Everything an agent does over MCP, a script can do over REST. The Page lifecycle lands under /api/platform/v1/pages - create, validate, render, update, archive - authenticated with an API key. Use this for automation and backend services; AI agents should use MCP instead.
One base, one key
The Page endpoints live under https://app.amdahl.co/api/platform/v1/pagesand authenticate with the same API key as the rest of the REST surface — an X-API-Key header (see Using the API). Tenancy comes from the key, so there is no business id in the path. Page JSON is snake_case, matching the backend columns and the verifier contracts.
The lifecycle endpoints
Every action you can take on a Page is one REST call:
GET /pages— list the workspace’s Pages (lightweight summaries, no spec body). Scopepages:read.GET /pages/:id— read one Page in full (itsspec+declared_queries). Scopepages:read.POST /pages— create a Page; the spec must pass the verifier. Scopepages:write.PATCH /pages/:id— patch a Page (only the fields you send change; the new spec is re-verified). Scopepages:write.POST /pages/validate— dry-run the verifier: no write, just the verdict. Scopepages:read.POST /pages/:id/render— run a saved Page’s declared queries with params and get the rows back. Scopepages:read.DELETE /pages/:id— archive a Page (reversible; the row and version history are kept). Scopepages:write.POST /pages/:id/hard-delete— permanently delete a Page. Not reversible. Scopepages:delete.
Plus the two read-only template endpoints, GET /pages/templates and GET /pages/templates/:slug (see Templates), both pages:read.
The validate, then create loop
validate is a separate endpoint from createon purpose: it lets you check a candidate spec without writing anything, so a script converges the same way the editor and an agent do — emit, validate, fix, create. Send the spec + declared queries to validate first:
curl https://app.amdahl.co/api/platform/v1/pages/validate \
-H "X-API-Key: amdahl_sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"spec": {
"version": 1,
"root": {
"type": "Section",
"children": [
{ "type": "Heading", "props": { "text": "Open pipeline by stage" } },
{ "type": "BarChart", "props": { "data": { "$query": "deals" }, "x": "stage", "y": "count" } }
]
}
},
"declared_queries": [
{ "name": "deals", "source": "sql", "sql": "SELECT stage, COUNT(*) AS count FROM interactions GROUP BY stage" }
]
}'The verdict tells you whether every stage passed, and why if not:
{
"ok": true,
"stages": { "compile": true, "query": true, "bind": true },
"errors": [],
"has_custom_nodes": false
}On failure, ok is false and errors lists each problem with a stage, message, and (when it can be localized) the field / line / column. Fix just that part and re-validate. Once ok is true, POST /pages with the same spec + declared_queriesbody to create the Page — both endpoints share one verifier, so a spec that validated will store.
Composing from the catalog is the path of least resistance. A spec that uses a Custom (sandbox TSX) node comes back with has_custom_nodes: true, and creating it additionally requires the pages:adminscope — a non-admin key is rejected (see Authoring).
Every read and write returns a console_url
The list, get, create, and update responses each carry a console_url — the workspace link to open that Page in the console (https://console.amdahl.co/<workspace-slug>/pages/<id>). After a script creates or edits a Page, hand that URL to whoever should review it; you don’t have to compose it yourself. It is nullonly in the rare case the workspace slug can’t be resolved. Archive and hard-delete don’t return one — the Page is gone.
Rendering a saved Page
renderruns a saved Page’s declared queries against the tenant’s data — scoped to the calling key the same way a human viewer is scoped — and returns a per-query result map. It is REST-only (an agent authors Pages, it does not render them). Pass the Page’s runtime params if it declares any:
curl https://app.amdahl.co/api/platform/v1/pages/PAGE_ID/render \
-H "X-API-Key: amdahl_sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "params": {} }'The response is a { results } envelope keyed by each declared-query name; every entry carries its rows plus an optional error. One query failing surfaces as that key’s error without blanking the others.
Archive vs hard-delete
Two ways to remove a Page, and they are not the same. DELETE /pages/:id archivesit — the Page stops rendering and leaves the sidebar, but the row and its version history are retained, so it is reversible. pages:writecovers it. POST /pages/:id/hard-delete is permanent— it drops the row and its bindings for good, and needs the dedicated pages:deletescope. Prefer archive unless you genuinely mean “remove this forever.”
Per-page access grants
Who can open or edit a single Page is governed by per-page grants, on the admin-only /console trust surface (a non-admin key gets a 403). A grant binds a subject — user, api_key, or role— to the Page with a verb: read (open + render) or write (also edit).
GET /console/pages/:id/grants— list a Page’s grants.PUT /console/pages/:id/grants— upsert one subject’s grant (replace semantics on the subject key).DELETE /console/pages/:id/grants/:subjectType/:subjectId— remove a grant; the subject falls back to its workspace-level access.
With no grants on a Page, every workspace member who can see the Pages section can open it (the “no grants = full access” default). See Access for how this composes with the section surface and the per-viewer data scope.
The scopes, at a glance
pages:read— list / get / validate / render / templates.pages:write— create / update / archive.pages:delete— hard-delete (permanent).pages:admin— additionally required to create or update a spec that contains aCustomnode.
The same operations over MCP
An AI agent reaches the same authoring surface over MCP instead — the pages tool exposes list / get / validate / create / update / archive, and now delete too (the permanent hard-delete, gated by the dedicated pages:deletescope, for full-CRUD parity with the agent’s artifact and knowledge-base surfaces), plus page:// reads, with the identical verifier underneath (see Authoring via MCP). render is the one REST-only action, deliberately absent from the agent surface: rendering belongs to the host.