REST API
Drive MadMax from CI, scripts, or your own services over /api/v1.
Everything the workflows index can do, over HTTP: list, create (including from an exported document), edit the graph, activate, run, and read execution history.
Authentication
Mint a token in Settings → developer. Tokens are pinned to one
workspace and scoped read or read + build. Two headers are
mandatory on every call:
Settings → developer: mint workspace-pinned tokens and grab the quick-start snippets.
Authorization: Bearer mmx_...
Content-Type: application/vnd.api+json
Sanity-check your setup:
curl https://your-instance/api/v1/me \
-H "Authorization: Bearer $MADMAX_API_TOKEN" \
-H "Content-Type: application/vnd.api+json"
What's on the surface
Every route, straight from the OpenAPI spec (the same one
/api/docs renders). :id is the workflow / store /
execution UUID; :key is a store row's key.
# Workflows
GET /api/v1/workflows Workflows in the token's workspace, newest first.
POST /api/v1/workflows Create a workflow — bare (name only) or from a document: a
GET /api/v1/workflows/:id One workflow by id, pinned-workspace only.
PATCH /api/v1/workflows/:id Rename and/or set the description. Nothing else is PATCHable.
DELETE /api/v1/workflows/:id
POST /api/v1/workflows/:id/activate Freeze the canvas into a snapshot and start firing triggers.
GET /api/v1/workflows/:id/calls The call graph around one workflow: `callers` (workflows whose call_workflow steps target it) an…
POST /api/v1/workflows/:id/deactivate Stop firing triggers; the snapshot survives for audit.
POST /api/v1/workflows/:id/duplicate Copy into the same workspace as "<name> (copy)"; connection bindings reset.
GET /api/v1/workflows/:id/export The workflow as a portable madmax/v1 document (env ids stripped).
PUT /api/v1/workflows/:id/graph Replace the live canvas graph (same write path as autosave). Runs auto-layout afterwards unless…
PATCH /api/v1/workflows/:id/move Move a workflow into a folder (folder_id, null = root) and/or reorder it (before_id / after_id).
POST /api/v1/workflows/:id/run Execute now. Active workflows run their pinned snapshot; draft: true runs the live canvas.
GET /api/v1/workflows/map The workspace's call graph laid out as the index's graph view: `systems` (connected groups of ≥2…
# Folders
GET /api/v1/folders Folders in the token's workspace.
POST /api/v1/folders Create a folder (one level deep — parent_id is optional).
GET /api/v1/folders/:id Folders in the token's workspace.
PATCH /api/v1/folders/:id Rename a folder.
DELETE /api/v1/folders/:id Delete a folder; its workflows move back to the root.
PATCH /api/v1/folders/:id/reorder Reorder a folder among its siblings — before_id / after_id (the neighbouring folders at the drop…
# Executions
GET /api/v1/executions Executions across the token's workspace, newest first.
GET /api/v1/executions/:id One execution by id, pinned-workspace only.
POST /api/v1/executions/:id/cancel Terminal stop for a pending/running/waiting execution — flips
GET /api/v1/workflows/:workflow_id/executions Executions for one workflow, newest first, pinned-workspace only.
# Execution steps
GET /api/v1/executions/:workflow_execution_id/steps
# Step catalog
GET /api/v1/step-templates Globally readable. Sorts by `sort_order` so the catalog
# Stores
GET /api/v1/stores Stores in the token's workspace, oldest first.
POST /api/v1/stores
GET /api/v1/stores/:id Store descriptor — name, slug, schema shape, row count.
PATCH /api/v1/stores/:id
DELETE /api/v1/stores/:id
# Store rows
GET /api/v1/stores/:store_id/rows Rows ascending by update time. Poll by following links.next, or restart from a timestamp with ?u…
GET /api/v1/stores/:store_id/rows/:key One row by its caller-defined key (unique per store).
PUT /api/v1/stores/:store_id/rows/:key Insert or replace one row by key. `row` must satisfy the store's schema; the row cap applies.
DELETE /api/v1/stores/:store_id/rows/:key Delete one row by key. 404 when the key doesn't exist in that store.
# Knowledge bases
GET /api/v1/knowledge-bases Knowledge bases in the token's workspace, oldest first.
POST /api/v1/knowledge-bases
GET /api/v1/knowledge-bases/:id Knowledge base descriptor.
PATCH /api/v1/knowledge-bases/:id
DELETE /api/v1/knowledge-bases/:id
GET /api/v1/knowledge-bases/:id/documents Document index + ingest status for this base.
POST /api/v1/knowledge-bases/:id/documents Ingest one text/markdown document into this base — chunked, embedded, searchable. Re-sending the…
# Memories
GET /api/v1/memories Memories in the token's workspace, oldest first.
POST /api/v1/memories
GET /api/v1/memories/:id Memory descriptor — window, compaction, recall settings.
PATCH /api/v1/memories/:id
DELETE /api/v1/memories/:id
Behavior worth knowing:
- Read-only tokens get exactly GETs — a write with a read token
is a
403telling you to mint a build-scoped one. - Two body shapes. JSON:API resource routes (list / read / create /
rename / delete, folders, moves) take the envelope
{"data": {"type": "...", "attributes": {...}}}. Action routes (/graph,/run,/activate,/export, store row writes) take a flat{"data": {<arguments>}}and answer plain JSON. The playground shows the exact skeleton for each. PUT /workflows/:id/graphruns auto-layout afterwards, the same pass the canvas and MCP run, so a graph you push lands laid out. Send"layout": falseto keep your own positions.- Store row writes (
PUT/DELETE /stores/:id/rows/:key) go through the same validated path as the Save-to-store step: the store's schema, its row cap, and your workspace all apply. - Requests are rate-limited per token — back off on
429(there's aRetry-After). - API access rides the Pro plan, same as MCP.
The call graph
Multi-workflow systems — an entry workflow fanning out through call workflow steps — are first-class on the API:
GET /api/v1/workflows/:id/calls # {"callers": [...], "callees": [...]} — distinct, with `active`
GET /api/v1/workflows/map # the index's graph view: systems + standalone
/calls is the question to ask before deactivating or deleting a
sub-workflow. /map returns the same layout the graph view draws:
each system carries columns (one per call depth, entries first),
edges as [caller_id, callee_id] pairs, its folder label, and
which members are stray (filed elsewhere). search and status
query params narrow the workflows before layout.
The machine-readable spec
Everything above is generated from an OpenAPI 3 document, and that document is public — point any tool at it and it knows every route, argument, and response shape without reading these pages:
GET https://madmax.build/api/docs/openapi.json # no token needed
GET https://madmax.build/api/v1/open_api # same spec, bearer-authed
What that's for:
- Your own MCP server or agent that drives MadMax over REST —
load the spec once and it can call any endpoint correctly, or hand
it to an LLM as the tool description. (The built-in MCP server at
/mcpis the richer option when you want MadMax's own tools; see MCP vs REST.) - A client already connected over MCP — it gets the same spec
without leaving the protocol: the
madmax://api/openapiresource, plusmadmax://docs/rest-api(this page as markdown). Theinitializehandshake tells it both exist. - Postman / Insomnia — import the URL as a collection.
- Client generators — a typed SDK in your language from the spec.
The spec is also checked into the repo at docs/api/openapi.json, so
you can diff API changes between releases without a running instance.
The interactive reference
The full endpoint reference lives at /api/docs — every
route with params, response codes, curl snippets, and a try it
panel that fires real requests with your token. The raw OpenAPI 3
spec is at /api/docs/openapi.json for Postman or codegen.
The 60-second tour
# create a workflow
curl -X POST $BASE/api/v1/workflows \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/vnd.api+json" \
-d '{"data": {"name": "From the API"}}'
# push a graph onto it
curl -X PUT $BASE/api/v1/workflows/$ID/graph \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/vnd.api+json" \
-d '{"data": {"graph": {"nodes": [...], "edges": [...]}}}'
# activate + run
curl -X POST $BASE/api/v1/workflows/$ID/activate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/vnd.api+json"
POST /workflows also accepts a whole document — a MadMax
export, an n8n export, or a bare {nodes, edges} graph — and
reports what's left to finish (placeholders + required connections).
Every verb answers the same JSON:API envelope the resource routes
use, so data.id / data.attributes.* read the same everywhere;
import reports ride in meta:
{
"data": {
"type": "workflow",
"id": "01a0…",
"attributes": {"name": "Doc Import", "status": "inactive",
"created_at": "…", "updated_at": "…"}
},
"meta": {"placeholders": [...], "requirements": {...}}
}