Skip to content

HTTP

Every route below needs the portal token — ?token=<token> once, after which the praxis_token cookie (HTTP-only, SameSite=Strict) carries it — except GET /api/healthz. Without it: 401 Unauthorized. Open the URL printed by praxis.

Errors are JSON {"detail": "..."}. Statuses worth knowing everywhere:

Status Means
409 a job is running — wait for it to finish a model build holds the machine-wide job lock
503 no Cube deployment is configured no model store address is stored
503 (sandbox) the sandbox is not configured or did not answer
422 the request body failed validation

Streaming routes answer text/event-stream: data: {event}\n\n frames carrying the event format. They are POSTs, read with fetch. Closing the connection cancels the turn.

The route list is held by tests/surface.json, and tests/test_docs.py fails when a route there is missing from this page.

Onboarding — /api/onboarding

Method Path Does
GET /api/onboarding/connection The current connection, or null
POST /api/onboarding/connection Store warehouse settings and mint the three secrets. Body {description, driver, values}. 400 for a missing required setting or an unknown key
PATCH /api/onboarding/connection Edit description or settings; a blank value keeps what is stored
POST /api/onboarding/connection/probe Check health now, ignoring the 15 s cache
GET /api/onboarding/deployment/{service}/compose Download the compose file for semantic or sandbox. The only route that returns credential values. 404 before connecting; 503 if it cannot be rendered
PUT /api/onboarding/deployment Check and store {store_url, sandbox_url}. Returns {checked, connection}; only addresses that pass are saved
GET /api/onboarding/pipeline/tables The cached tables for the picker. Never opens the warehouse
POST /api/onboarding/pipeline/tables/closure Tables a selection pulls in through joins, and tables nothing can join to. Body {schema_name, tables}
GET /api/onboarding/pipeline/job The running job and its buffered lines; {"id": null} when idle
DELETE /api/onboarding/pipeline/job Cancel the running job
GET /api/onboarding/pipeline/job/stream The job's narration as SSE, replayed from the buffer on connect
POST /api/onboarding/pipeline/{stage} Start a stage (read, cubes, describe, views). 404 unknown stage; 409 a job is already running

Model editor — /api/ide

Workspace refusals map to: ref → 409, path → 400, missing → 404, rejected → 400, store → 502.

Method Path Does
GET /api/ide/model The compiled model on ?ref= (default main). 200 with serving: false when Cube is down
GET /api/ide/model/summary One line per entity
GET /api/ide/refs Branches, each with its number of changed files
POST /api/ide/refs Create a branch. Body {name, from_ref}
DELETE /api/ide/refs/{ref} Delete a branch and its draft. 409 for main
GET /api/ide/refs/{ref}/diff Changed files, the patch, and which saved queries would break
POST /api/ide/refs/{ref}/merge Land the branch on main. ?force= to merge past broken tiles, ?delete_after=
GET /api/ide/files Files on ?ref=, with draft state
GET /api/ide/files/{path} Read a file from the draft
PUT /api/ide/files/{path} Save a file into the draft. Body {ref, text}. Compiles nothing
DELETE /api/ide/files/{path} Remove a file from the draft
POST /api/ide/discard Reset the draft, or one path of it
POST /api/ide/check Validate: compile the draft on a throwaway ref and check saved queries. 200 even when rejected
POST /api/ide/commit Publish: one commit of the draft to the branch
POST /api/ide/chats Create a model-editor chat bound to a branch
POST /api/ide/chats/{chat_id}/messages One editor turn, SSE. Body {question}

Chats — /api/chats

Method Path Does
GET /api/chats Question chats, most recent first
POST /api/chats Create a chat. Body {title}
PATCH /api/chats/{chat_id} Rename. Body {title}
DELETE /api/chats/{chat_id} Delete the chat, its events and results, and its sandbox workspace
GET /api/chats/{chat_id}/events The transcript
POST /api/chats/{chat_id}/messages One question turn, SSE. Body {question, files}
POST /api/chats/{chat_id}/stop Cancel the turn in flight
POST /api/chats/{chat_id}/approve Answer a script approval. Body {decision: "yes" \| "always" \| "no"}. 404 if nothing is waiting
POST /api/chats/{chat_id}/files Upload (multipart file) to the sandbox; returns its profile. 413 over 100 MB
GET /api/chats/{chat_id}/files/{path} Download a workspace file
GET /api/chats/{chat_id}/preview/{path} The head of a workspace file, as a table or text
DELETE /api/chats/{chat_id}/files/{path} Delete a workspace file

Results — /api/results

Method Path Does
GET /api/results/{result_id} One page of a stored result: ?offset=&limit= (default 200)
GET /api/results/{result_id}/csv The whole result as CSV

A result is addressed by id alone because the browser finds it from a tool_end event.

Dashboards — /api/dashboard

Method Path Does
GET /api/dashboard Boards, with tile count and last refresh
POST /api/dashboard Create a board. Body {title}
GET /api/dashboard/{dashboard_id} Tiles, layout, last rows, and a compile pass over every tile
PATCH /api/dashboard/{dashboard_id} Rename
DELETE /api/dashboard/{dashboard_id} Delete
POST /api/dashboard/{dashboard_id}/refresh Run every tile — all compile or none execute. 502 when Cube is not answering
POST /api/dashboard/{dashboard_id}/layout Save a layout version. Body {spec, note}. 400 with the validation errors

Flows — /api/flows

Method Path Does
GET /api/flows Every Flow, with whether it runs and its errors and warnings
POST /api/flows Create a Flow folder. Body {name, title, description}. 409 if it exists
GET /api/flows/{flow} One Flow
GET /api/flows/{flow}/files Its definition files
GET /api/flows/{flow}/files/{path} Read one, as text
PUT /api/flows/{flow}/files/{path} Write one, then re-check the Flow. Body {text}. Returns {path, ok, errors, warnings}
DELETE /api/flows/{flow}/files/{path} Delete one; an emptied Phase folder goes too
GET /api/flows/{flow}/runs Its runs, newest first
POST /api/flows/{flow}/runs Start a run. Body {brief}. Returns {run_id, current}. 422 if the Flow cannot run
GET /api/flows/{flow}/runs/{run_id} run.json, plus each Phase's plan
DELETE /api/flows/{flow}/runs/{run_id} Delete a run. 409 while it is going
GET /api/flows/{flow}/runs/{run_id}/events The run's events.jsonl
POST /api/flows/{flow}/runs/{run_id}/advance Run Phases until the next Gate, SSE. 409 if done, failed, or already going
POST /api/flows/{flow}/runs/{run_id}/stop Stop the Phase in progress
POST /api/flows/{flow}/runs/{run_id}/approve Answer a script approval. Body {choice} (default "no")
GET /api/flows/{flow}/runs/{run_id}/artifacts/{path} Read a run file, as text
PUT /api/flows/{flow}/runs/{run_id}/artifacts/{path} Edit an output at its Gate. 409 if not at a Gate or not that Phase's output. Later Phases become stale
GET /api/flows/{flow}/runs/{run_id}/preview/{path} The head of a run file, with every row counted
POST /api/flows/{flow}/runs/{run_id}/phases/{dir_name}/rerun Point the run back at a Phase. Does not execute it
POST /api/flows/chats Get or create a Flow's authoring chat. Body {flow}
GET /api/flows/chats/{chat_id}/events Its transcript
POST /api/flows/chats/{chat_id}/messages One authoring turn, SSE
POST /api/flows/chats/{chat_id}/stop Cancel the turn

chats is reserved as a Flow name, because /api/flows/chats would otherwise be a Flow.

MCP settings — /api/mcp

Method Path Does
GET /api/mcp Endpoint URL, enabled flag, token, tool list, and client configurations
POST /api/mcp/enabled Switch MCP on or off. Body {enabled}
POST /api/mcp/rotate Mint a new MCP token

Other

Method Path Does
GET / The browser app. 503 if it has not been built
GET /api/healthz {"ok": true}. No token needed