Developers

REST API

Authenticate with a workspace-pinned mmx_ token, then list, create, activate and run workflows, read executions, and manage stores, knowledge and memories.

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, either 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 notes. 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), a bundle (bundle_id), or the top level (both null), a…
POST /api/v1/workflows/:id/run                       Execute now. Active workflows run their pinned snapshot; draft: true runs the live canvas; input…
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/notes                      Set the folder's notes.
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 document into this base: chunked, embedded, searchable. Re-sending the same sour…
DELETE /api/v1/knowledge-bases/:id/documents         Remove one document (and its chunks) by source_name. Deleting a name that isn't there is a no-op…

# 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

# workflow-bundle
GET /api/v1/bundles                                  Bundles in the token's workspace, with their accounts.
POST /api/v1/bundles                                 Create a bundle, optionally inside a folder (folder_id).
GET /api/v1/bundles/:id                              Bundles in the token's workspace, with their accounts.
PATCH /api/v1/bundles/:id                            Rename a bundle.
DELETE /api/v1/bundles/:id                           Delete the bundle; its workflows move to its folder, or are deleted with with_workflows.
POST /api/v1/bundles/:id/apply                       Pick single accounts automatically and fill every empty step. Safe to repeat.
POST /api/v1/bundles/:id/apps/:provider              Add an app to the bundle before any workflow uses it.
DELETE /api/v1/bundles/:id/apps/:provider            Remove an app no workflow in the bundle uses.
PUT /api/v1/bundles/:id/connections/:provider        Set the bundle's account for one app; fills empty steps, re-points bundle-filled ones.
PATCH /api/v1/bundles/:id/move                       Put the bundle inside a folder (folder_id), or at the top level (null).
PATCH /api/v1/bundles/:id/notes                      Set the bundle's notes.
GET /api/v1/bundles/:id/settings                     The bundle's apps, their accounts, and what still waits for one.
POST /api/v1/bundles/:id/unbundle                    Turn the bundle back into a folder; every workflow keeps its accounts.

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, so 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 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"

# run with a trigger payload; the first step sees it as payload.*
curl -X POST $BASE/api/v1/workflows/$ID/run \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  -d '{"data": {"inputs": {"order_id": "ord_123", "amount": 42}}}'

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": {...}}
}