Nine tools let an agent build and manage Vlozi Flows automations — trigger-driven chains of tool calls across every other namespace.
All endpoints follow the pattern POST https://mcp.vlozi.app/tools/flow.<name> with Authorization: Bearer ls_xxx. All responses use this envelope:
{ "data": <payload>, "error": null } // success
{ "data": null, "error": "..." } // failure (with HTTP 4xx/5xx)IMPORTANT
A flow's shape today is a single linear chain — the trigger connects to exactly one node, and no node may branch to more than one other node. create_flow/update_flow reject anything else with 422. Flows are always created as draft; enable_flow is a separate step.
Discovery
flow.list_nodes
List every trigger type and every action node available to wire into a flow.
Scope: flow:flows.read
Input: none
Response: { triggers: { type, description, configSchema, payloadSchema, source }[], nodes: { catalogRef, label, category, description, inputSchema, outputSchema, mappableFields }[] }
NOTE
Call this first — every node in a flow's definition must reference a real catalogRef from this list (e.g. blog.create_draft), and every trigger's type must be one this endpoint reports. flow.* tools themselves never appear as nodes — a flow can't trigger another flow.
flow.list_flows
List every flow in the workspace.
Scope: flow:flows.read
Input: none
Response: { flows: Automation[] } — full rows (id, name, status, trigger, definition, requiredPermissions, createdByKind, consecutiveFailures, disabledReason, version, ...), newest-updated first, no pagination
flow.get_flow
Get one flow's full definition.
Scope: flow:flows.read
Input: { id: string } (required — flw_...)
Response: { flow: Automation }
flow.get_run_history
Get a flow's recent runs, with per-step results.
Scope: flow:flows.read
Input: { id: string, limit?: number } (id required; limit default 20, max 100)
Response: { runs: { id, status: "running" | "success" | "failed" | "partial", eventId, triggerPayload, error, startedAt, finishedAt, steps: { nodeId, catalogRef, status, input, output, error, attempts }[] }[] }, newest first
NOTE
When a step fails, every downstream step is marked skipped rather than left pending, so a run's history always reads as a complete, resolved picture. Runs older than 90 days are purged automatically — they disappear from history rather than being paginated away.
Authoring
flow.create_flow
Create a new automation.
Scope: flow:flows.create
Input:
| Field | Type | Notes |
|---|---|---|
name |
string (required) |
|
trigger |
{ type: string, config: object } (required) |
type must be a real trigger from list_nodes |
definition |
{ nodes: [{ id, catalogRef, inputs }], edges: [{ from, to }] } (required) |
edges[].from is a node id or "trigger" |
Response: { id: string, status: "draft", preview: { name, trigger, steps: [{ nodeId, catalogRef, inputs }], requiredPermissions: string[] } }
NOTE
inputs values can template in data via {{trigger.<path>}} or {{nodes.<id>.output.<path>}} — dotted-path only, no expression language. Always created as draft, regardless of who creates it — enabling is separate. Validation is strict: unknown trigger types, missing config fields, non-linear graphs, and template references to a field that doesn't actually exist on the source's schema all fail with 422 and a details array. Your workspace plan caps how many flows you can have (drafts included) — exceeding it returns 402, not 422.
curl -X POST https://mcp.vlozi.app/tools/flow.create_flow \
-H "Authorization: Bearer $VLOZI_API_KEY" \
-d '{
"name": "Welcome new subscribers",
"trigger": {"type": "newsletter.subscriber.added", "config": {}},
"definition": {
"nodes": [{"id": "n1", "catalogRef": "newsletter.send_email", "inputs": {"to": "{{trigger.email}}", "subject": "Welcome!", "html": "<p>Thanks for subscribing.</p>"}}],
"edges": [{"from": "trigger", "to": "n1"}]
}
}'flow.update_flow
Replace a draft flow's definition. Not a partial patch — send the full name/trigger/definition again.
Scope: flow:flows.create
Input: { id: string, name: string, trigger: object, definition: object } (all required)
Response: { preview: object } (same shape as create_flow's preview)
WARNING
Only a draft flow can be edited — an enabled or disabled flow returns 409. To edit a live flow, disable_flow it first, then update, then enable_flow again.
Lifecycle
flow.enable_flow
Turn a flow on.
Scope: flow:flows.manage
Input: { id: string } (required)
Response: { status: "enabled" }
NOTE
Resets the flow's failure counter, so re-enabling a flow that was auto-disabled for repeated failures gives it a clean start. Returns 403 if you don't hold every permission the flow's steps require (an owner-level key bypasses this check). If the flow's trigger is schedule.cron, be aware it only fires if the platform's Cloudflare cron capacity allows it — ask your workspace owner if a scheduled flow never seems to run.
flow.disable_flow
Turn a flow off without losing anything.
Scope: flow:flows.manage
Input: { id: string, reason?: string } (id required)
Response: { status: "disabled" }
NOTE
Keeps the flow's definition and full run history — it just stops matching new trigger events. The same status also gets set automatically after 5 consecutive failed runs, distinguishable via disabledReason.
flow.delete_flow
Permanently delete a flow.
Scope: flow:flows.manage
Input: { id: string } (required)
Response: { deleted: true }
CAUTION
Irreversible, hard delete. Confirm with the user before calling.