Developers

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 — token minting and the REST quick start 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 403 telling 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/graph runs auto-layout afterwards, the same pass the canvas and MCP run, so a graph you push lands laid out. Send "layout": false to 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 a Retry-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 /mcp is 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/openapi resource, plus madmax://docs/rest-api (this page as markdown). The initialize handshake 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": {...}}
}