Workflows API

Create multi-step automations triggered by voice commands

Workflows API

Define multi-step workflows that voice agents can execute. Workflows support branching, parallel execution, and integration with MCP servers and webhooks.

Base URL: https://api.ferni.ai/api/v2/developers/workflows


Create Workflow

Create a new workflow definition.

POST /workflows

Request Body

{
  "name": "Daily Standup",
  "description": "Summarizes today's calendar for morning standup",
  "trigger": {
    "type": "voice_command",
    "config": { "command": "start standup" }
  },
  "nodes": [
    { "id": "start", "type": "start", "name": "Start" },
    { "id": "fetch-calendar", "type": "mcp_call", "name": "Fetch Calendar", "config": {
      "serverId": "mcp_google_calendar",
      "toolName": "calendar.getEvents",
      "arguments": { "date": "{{today}}", "maxResults": 10 }
    }},
    { "id": "summarize", "type": "llm_prompt", "name": "Summarize", "config": {
      "prompt": "Summarize these meetings: {{fetch-calendar.result | json}}",
      "outputVariable": "summary"
    }},
    { "id": "speak", "type": "speak", "name": "Tell User", "config": {
      "text": "{{summary}}"
    }},
    { "id": "end", "type": "end", "name": "End" }
  ],
  "edges": [
    { "id": "e1", "sourceId": "start", "targetId": "fetch-calendar" },
    { "id": "e2", "sourceId": "fetch-calendar", "targetId": "summarize" },
    { "id": "e3", "sourceId": "summarize", "targetId": "speak" },
    { "id": "e4", "sourceId": "speak", "targetId": "end" }
  ],
  "entryNodeId": "start",
  "enabled": true
}

Parameters

Field Type Required Description
name string Yes Workflow name
description string No Human-readable description
trigger object Yes How the workflow is triggered
nodes array Yes Workflow nodes (steps)
edges array Yes Connections between nodes
entryNodeId string Yes Starting node ID
enabled boolean No Active state (default: true)
personaId string No Limit to specific persona
timeout number No Max execution time in ms

Response

{
  "success": true,
  "data": {
    "id": "wf_abc123xyz",
    "name": "Daily Standup",
    "description": "Summarizes today's calendar for morning standup",
    "status": "active",
    "trigger": { "type": "voice_command", "config": { "command": "start standup" } },
    "nodeCount": 5,
    "enabled": true,
    "version": "1",
    "createdAt": "2026-01-11T10:00:00Z",
    "updatedAt": "2026-01-11T10:00:00Z"
  }
}

List Workflows

Retrieve all workflows.

GET /workflows

Query Parameters

Field Type Description
status string Filter: active, error, disabled
trigger string Filter by trigger type
limit number Max results (default: 50)
offset number Pagination offset

Response

{
  "success": true,
  "data": [
    {
      "id": "wf_abc123xyz",
      "name": "Daily Standup",
      "description": "Summarizes today's calendar",
      "status": "active",
      "trigger": { "type": "voice_command" },
      "nodeCount": 5,
      "enabled": true,
      "lastExecutedAt": "2026-01-11T09:00:00Z",
      "executionCount": 47,
      "createdAt": "2026-01-11T10:00:00Z"
    }
  ]
}

Get Workflow

Retrieve full workflow definition.

GET /workflows/:id

Response

Returns complete workflow including all nodes and edges.


Update Workflow

Update an existing workflow.

PUT /workflows/:id

Updates create a new version. Previous versions are retained for rollback.


Delete Workflow

Permanently remove a workflow.

DELETE /workflows/:id

Execute Workflow

Manually trigger a workflow execution.

POST /workflows/:id/execute

Request Body

{
  "input": {
    "customVariable": "value"
  }
}

Response

{
  "success": true,
  "data": {
    "executionId": "exec_xyz789",
    "workflowId": "wf_abc123xyz",
    "status": "running",
    "startedAt": "2026-01-11T10:30:00Z"
  }
}

Test Workflow (Dry Run)

Execute without side effects.

POST /workflows/:id/test

Request Body

{
  "dryRun": true,
  "input": {
    "customerName": "Acme Corp"
  }
}

Response

{
  "success": true,
  "data": {
    "executionId": "exec_test_123",
    "status": "completed",
    "dryRun": true,
    "nodeResults": {
      "fetch-calendar": { "status": "skipped", "reason": "dry_run" },
      "summarize": { "status": "skipped", "reason": "dry_run" }
    },
    "duration": 50
  }
}

List Executions

Get execution history for a workflow.

GET /workflows/:id/executions

Query Parameters

Field Type Description
status string Filter: running, completed, failed, cancelled
limit number Max results (default: 50)

Response

{
  "success": true,
  "data": [
    {
      "id": "exec_xyz789",
      "workflowId": "wf_abc123xyz",
      "status": "completed",
      "triggeredBy": "voice",
      "startedAt": "2026-01-11T09:00:00Z",
      "completedAt": "2026-01-11T09:00:05Z",
      "duration": 5000
    }
  ]
}

Get Execution Details

Get detailed results for a specific execution.

GET /workflows/:id/executions/:execId

Response

{
  "success": true,
  "data": {
    "id": "exec_xyz789",
    "workflowId": "wf_abc123xyz",
    "status": "completed",
    "triggeredBy": "voice",
    "startedAt": "2026-01-11T09:00:00Z",
    "completedAt": "2026-01-11T09:00:05Z",
    "duration": 5000,
    "nodeResults": {
      "fetch-calendar": {
        "status": "completed",
        "startedAt": "2026-01-11T09:00:00Z",
        "duration": 1200,
        "result": { "events": [{ "title": "Standup", "time": "9:30 AM" }] }
      },
      "summarize": {
        "status": "completed",
        "duration": 2100,
        "result": "You have one meeting today: Standup at 9:30 AM."
      },
      "speak": {
        "status": "completed",
        "duration": 100
      }
    },
    "variables": {
      "summary": "You have one meeting today: Standup at 9:30 AM."
    }
  }
}

Trigger Types

Voice Command

Triggered when user says a phrase.

{
  "type": "voice_command",
  "config": {
    "command": "start standup"
  }
}

Schedule

Triggered on a cron schedule.

{
  "type": "schedule",
  "config": {
    "cron": "0 9 * * 1-5",
    "timezone": "America/New_York"
  }
}

API

Triggered via the execute endpoint.

{
  "type": "api"
}

Event

Triggered by a webhook event.

{
  "type": "event",
  "config": {
    "eventType": "session.ended"
  }
}

Node Types

start

Entry point for the workflow.

{ "id": "start", "type": "start", "name": "Start" }

end

Exit point for the workflow.

{ "id": "end", "type": "end", "name": "End" }

mcp_call

Execute a tool from an MCP server.

{
  "id": "lookup",
  "type": "mcp_call",
  "name": "Lookup Customer",
  "config": {
    "serverId": "mcp_crm",
    "toolName": "lookup_customer",
    "arguments": {
      "query": "{{input.customerName}}"
    }
  }
}

webhook

Call an external HTTP endpoint.

{
  "id": "notify",
  "type": "webhook",
  "name": "Notify Slack",
  "config": {
    "url": "https://hooks.slack.com/services/xxx",
    "method": "POST",
    "headers": { "Content-Type": "application/json" },
    "body": { "text": "Workflow completed: {{workflowName}}" }
  }
}

llm_prompt

Generate text using the language model.

{
  "id": "summarize",
  "type": "llm_prompt",
  "name": "Summarize",
  "config": {
    "prompt": "Summarize this in one sentence: {{data}}",
    "model": "gemini-2.0-flash",
    "outputVariable": "summary"
  }
}

condition

Branch based on an expression.

{
  "id": "check",
  "type": "condition",
  "name": "Has Events?",
  "config": {
    "expression": "fetch-calendar.result.events.length > 0"
  }
}

Connect with conditional edges:

{ "sourceId": "check", "targetId": "summarize", "condition": "true" },
{ "sourceId": "check", "targetId": "no-events", "condition": "false" }

parallel

Execute multiple branches simultaneously.

{
  "id": "parallel-fetch",
  "type": "parallel",
  "name": "Fetch All",
  "config": {
    "branches": [
      { "entryNodeId": "fetch-calendar" },
      { "entryNodeId": "fetch-tasks" },
      { "entryNodeId": "fetch-emails" }
    ]
  }
}

wait

Pause execution.

{
  "id": "wait",
  "type": "wait",
  "name": "Wait for Approval",
  "config": {
    "duration": 60000,
    "event": "approval.received"
  }
}

set_variable

Store a value for later use.

{
  "id": "set-status",
  "type": "set_variable",
  "config": {
    "variable": "ticketStatus",
    "value": "resolved"
  }
}

speak

Output to the user.

{
  "id": "speak",
  "type": "speak",
  "name": "Tell User",
  "config": {
    "text": "{{summary}}"
  }
}

activity

Log a custom activity.

{
  "id": "log",
  "type": "activity",
  "config": {
    "type": "workflow_completed",
    "name": "Daily Standup",
    "data": { "meetingCount": "{{fetch-calendar.result.events.length}}" }
  }
}

Variable Interpolation

Use {{expression}} syntax to reference:

Reference Example
Input variables {{input.customerName}}
Node results {{nodeId.result}}
Workflow variables {{variableName}}
Built-ins {{today}}, {{now}}, {{userId}}

Filters

Apply transformations with pipe syntax:

{{data | json}}           → JSON stringify
{{text | uppercase}}      → Uppercase
{{list | first}}          → First item
{{number | round}}        → Round number
{{date | format('MMM d')}} → Format date

Error Handling

Per-Node Error Handling

{
  "id": "risky",
  "type": "webhook",
  "config": { ... },
  "onError": {
    "type": "goto",
    "targetNodeId": "error-handler"
  }
}

Retry Policy

{
  "id": "flaky",
  "type": "webhook",
  "config": { ... },
  "retry": {
    "maxAttempts": 3,
    "backoffMs": 1000,
    "backoffMultiplier": 2
  }
}

Related