Docs
Pages

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). Scope pages:read.
  • GET /pages/:id — read one Page in full (its spec + declared_queries). Scope pages:read.
  • POST /pages— create a Page; the spec must pass the verifier. Scope pages:write.
  • PATCH /pages/:id— patch a Page (only the fields you send change; the new spec is re-verified). Scope pages:write.
  • POST /pages/validate— dry-run the verifier: no write, just the verdict. Scope pages:read.
  • POST /pages/:id/render— run a saved Page’s declared queries with params and get the rows back. Scope pages:read.
  • DELETE /pages/:id— archive a Page (reversible; the row and version history are kept). Scope pages:write.
  • POST /pages/:id/hard-delete— permanently delete a Page. Not reversible. Scope pages: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:

bash
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:

json
{
  "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:

bash
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 a Custom node.

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.