---
title: "Create workflow"
method: POST
path: "/v1/workflows"
tags: ["Workflows"]
---

# Create workflow

`POST /v1/workflows`

Create a branching conversation workflow (draft) from a node/edge graph. Created in `draft` status; activate it to start matching inbound messages. The graph is validated structurally; completeness (a trigger node + reachable entry) is required at activation.

## Request body

- object
  - `profileId` string, required
  - `accountId` string, required
  - `platform` 'whatsapp' | 'instagram' | 'facebook' | 'telegram' | 'twitter' | 'bluesky' | 'reddit'
  - `name` string, required
  - `description` string
  - `nodes` WorkflowNode[]
    - `id` string, required — Stable node id referenced by edges
    - `type` 'trigger' | 'send_message' | 'wait_for_reply' | 'condition' | 'set_variable' | 'delay' | 'webhook' | 'ai' | 'handoff' | 'start_call' | 'a_b_split' | 'set_field' | 'enroll_sequence' | 'add_tag' | 'remove_tag' | 'end', required — Node kind. The 16 supported types break into four groups: messaging (send_message), control flow (trigger, condition, delay, wait_for_reply, a_b_split, end), data ops (set_variable, set_field, add_tag, remove_tag, enroll_sequence), integrations (webhook, ai, handoff, start_call).
    - `config` object — Type-specific settings. All string fields support `{{variable}}` interpolation against the run's variable bag (resolved at execution time). **trigger**: `{ triggerType: inbound_message|api_call|whatsapp_event, keywords:[string], matchType: any|contains|exact|regex, onlyFirstMessage:boolean, eventType: message_sent|message_delivered|message_read|message_failed|reaction }`. Default `triggerType` is `inbound_message` for legacy nodes. `eventType` is only honored when `triggerType` is `whatsapp_event` (WhatsApp-only). **send_message**: `{ messageType: text|template|media|interactive, text, template:{name,language,variableMapping}, media:{mediaType:image|video|audio|document, url,caption}, interactive }`. `template` and `interactive` are WhatsApp-only. `interactive.type` is inferred from the payload shape when omitted; payloads with neither `type` nor an inferable shape are rejected. **wait_for_reply**: `{ timeoutMinutes:int (max 43200), saveAs:string }`. Resume via the `'reply'` edge on inbound, or `'timeout'` edge after `timeoutMinutes` of silence. **condition**: `{ rules:[{ id, variable, operator: equals|not_equals|contains|not_contains|starts_with|ends_with|exists|not_exists|matches, value }] }`. First matching rule takes its `id` as the sourceHandle; otherwise `'default'`. **set_variable**: `{ assignments:[{ name, value }] }`. Run-scoped (lives only for this execution; use `set_field` for persistent values). **delay**: `{ delayMinutes:int (max 43200) }`. Suspends the run, resumes via timer. **webhook**: `{ url, method: GET|POST|PUT|PATCH|DELETE, headers, bodyTemplate, saveAs }`. SSRF-guarded (private/loopback/metadata IPs rejected). Response saved as `{ status, ok, body }` to `vars[saveAs]`. Edge: `'success'` on 2xx, `'error'` otherwise. **ai**: `{ provider: anthropic|openai|google|mistral|groq|openrouter, model, preset: smart|tools|cheap, systemPrompt, userPromptTemplate, saveAs, temperature, maxTokens, outputType: text|json, tools:[{ name, description, parameters }] }`. Set `provider` + `model` for BYOK (uses your stored API key); omit `provider` for the legacy Telnyx path. Edges: `'success'`, `'tool:<name>'` (model picked a tool), `'error'`. **handoff**: `{ note, assignTo }`. Terminates the run as `exited`, flags the conversation for a human operator. **start_call**: `{ to, forwardTo, requirePermissionFirst, recordingEnabled, saveAs }`. WhatsApp-only. `forwardTo` can be `tel:+E164`, `sip:user@host`, or `wss://…` (AI voice agent). Edges: `'success'`, `'permission_required'`, `'failed'`. **a_b_split**: `{ percentage: number 0-100 (default 50) }`. Random branch picker. Edges: `'a'` (with probability `percentage/100`), `'b'`. **set_field**: `{ field, value }`. Persistent custom field on the Contact (vs `set_variable` which is run-scoped). Field name is sanitized to `[A-Za-z0-9_]`. No-op on `api_call` runs (no contact). **enroll_sequence**: `{ sequenceId, saveAs }`. Enrolls the run's contact into a Sequence. Edges: `'success'`, `'error'`. **add_tag** / **remove_tag**: `{ tag }`. Push or pull a tag on the Contact. No-op on `api_call` runs. **end**: no config. Terminates the run as `completed`.
    - `position` object — Canvas coordinates (ignored by the executor; used by the visual builder).
      - `x` number
      - `y` number
  - `edges` WorkflowEdge[]
    - `id` string, required
    - `source` string, required — Source node id
    - `target` string, required — Target node id
    - `sourceHandle` string, nullable — Selects a branch output of a multi-output node. Null (or omitted) = the node's single/default output. Known handles per node type: - **condition** — a rule's `id`, or `'default'` (no rule matched) - **wait_for_reply** — `'reply'` (contact replied) | `'timeout'` (no reply in window) - **webhook** — `'success'` (2xx) | `'error'` (non-2xx / fetch failed) - **ai** — `'success'` (text/JSON response) | `'tool:<toolName>'` (model invoked that tool) | `'error'` (upstream failure / non-JSON in JSON mode) - **start_call** — `'success'` | `'permission_required'` | `'failed'` - **a_b_split** — `'a'` | `'b'` - **enroll_sequence** — `'success'` | `'error'`
  - `entryNodeId` string — The trigger node id; derived from the single trigger node if omitted

## Response `200`

Workflow created

- object
  - `success` boolean
  - `workflow` object
    - `id` string
    - `name` string
    - `description` string
    - `platform` string
    - `status` string
    - `nodeCount` integer
    - `entryNodeId` string
    - `createdAt` string, date-time

## Other responses

- `400` — Invalid graph (duplicate node ids, edges referencing missing nodes, or a WhatsApp-only node on another platform)
- `401` — Unauthorized

---

[API](https://skmtc.net/zernio/apis/zernio-api.md) · [All operations](https://skmtc.net/zernio/apis/zernio-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/zernio/zernio-api/revisions/f81ca70ea6b9/schema)
