Open & Self-Hostable
Run a lightweight subset of the approval engine on your own infrastructure with Docker — no external cloud dependency required.
ApprovalML is the open workflow definition language underneath Aptiwise. Every workflow — whether AI-generated or hand-authored — compiles to ApprovalML YAML: a portable, version-controllable format for approval routing, forms, and automation logic. The full language (forms, field types, roles, data sources) is what the hosted Aptiwise product runs. A self-hostable, open-source runtime also exists for a deliberately smaller slice of that language.
Open & Self-Hostable
Run a lightweight subset of the approval engine on your own infrastructure with Docker — no external cloud dependency required.
Gated MCP
Put human-in-the-loop approval gates in front of autonomous AI agents. An agent requests approval over MCP; a human approves or rejects by email, no login required.
Wrap Any MCP Server
approvalml -- npx -y @modelcontextprotocol/server-github spawns an existing stdio MCP server as a child process and re-exposes it with sensitive tools gated behind human approval — no changes to the upstream server required.
Declarative YAML
Steps, routing, and SLAs are defined as data, not code — reviewable in a pull request like any other config.
REST API
The full workflow lifecycle — submit, approve, reject, track — is available over REST, in addition to the MCP server.
The open-source runtime supports the step types below. It does not render dynamic forms or resolve roles through an org directory (only flat env-var role mapping — see below) — treat it as a headless approval gate, not a forms/workflow-designer product.
| Step type | Supported | Notes |
|---|---|---|
decision (human approval) |
✅ | Core feature — email link, no-login approve/reject |
notification |
✅ | Requires SMTP config or prints to stdout |
conditional_split |
✅ | Condition evaluation runs fully inline |
field_mapping |
✅ | Inline data transformation, no external calls |
asset |
✅ | Reads/writes named JSON records in PostgreSQL |
parallel_approval |
✅ | Multiple approvers, threshold logic |
spawn |
✅ | Fan-out to child workflow instances |
data_processor / data_source |
⚠️ | Requires a connector to be pre-seeded directly in the database — no UI |
The open-source runtime is intentionally headless and scoped to individual/personal use as a gated MCP approval server. It does not support:
approver: role_name resolves only through a flat APPROVALML_ROLE_<NAME> environment variable, not a directory lookupdata_processor and data_source steps require a connector row inserted manually into the database; there is no admin screen to configure oneThe runtime also works as a gate in front of an existing MCP server, not just as a standalone approval engine — useful for putting a human checkpoint in front of tools an AI agent already has access to (e.g. a GitHub or filesystem MCP server) without modifying that server:
{ "github-guarded": { "command": "uvx", "args": ["approvalml", "--", "npx", "-y", "@modelcontextprotocol/server-github"], "env": { "APPROVALML_APPROVER": "you@personal.com", "APPROVALML_NOTIFY": "slack:https://hooks.slack.com/services/XXX" } }}approvalml -- <command> <args> spawns the upstream MCP server as a child process and re-exposes its tools, classifying each one as:
get_/list_/search_/read_/describe_ naming, or an MCP readOnlyHint annotation) pass straight throughcreate_/update_/delete_/send_/execute_ naming, or a destructiveHint annotation) pend for a human approval before the real call is forwardedZero-config classification only auto-passes a tool when the naming heuristic and the tool’s MCP annotations agree; any disagreement or missing signal defaults to gate rather than silently allowing or blocking the call. An APPROVALML_CONFIG YAML file can override this with explicit glob rules for tighter control — either a flat rules:/default_action list routing individual tools to separately named workflows, or a guards.tools map where one file is both the classifier and the escalation workflow for the whole server (see MCP Tool Guards in the syntax reference).
APPROVALML_NOTIFY=<channel>:<target> (for example slack:https://hooks.slack.com/...) sends a notification to Slack or another webhook-based channel alongside — not instead of — the email approval link.
# 1. Clone and configuregit clone https://github.com/dickyj-grc/ApprovalML.gitcd ApprovalMLcp deployment/docker-compose.standalone.yml docker-compose.ymlcp deployment/.env.example .env# Edit .env: set SMTP_HOST, ADMIN_EMAIL, SECRET_KEY
# 2. Startdocker compose up -d
# 3. Load your workflow YAMLsapprovalml load ./workflows/vendor-invoice.yaml| Feature | Open-source runtime | Aptiwise (hosted SaaS) |
|---|---|---|
Approval engine (decision, parallel_approval, spawn, etc.) |
✅ | ✅ |
| Email approvals (no login) | ✅ | ✅ |
| MCP server / gated MCP | ✅ | ✅ |
Wrap an existing stdio MCP server (approvalml -- <cmd>) |
✅ | ✅ Same CLI, pointed at Aptiwise via APPROVALML_API_URL |
| Dynamic form rendering (field types, layouts) | ❌ | ✅ |
| Role-based approval routing | ⚠️ Flat env-var mapping only | ✅ Org directory & hierarchy |
| Data source / connector UI | ❌ Manual DB seeding only | ✅ |
| AI workflow generation | ❌ | ✅ |
| Admin & workflow designer UI | ❌ Not included | ✅ |
| Multi-tenant (multiple companies) | ❌ Single company | ✅ |
| SLA escalation | ✅ (via cron tick endpoint) | ✅ |
| Reporting / analytics | ❌ | ✅ |
Syntax Reference
Field Types
Approval Types
Form Layouts