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

FieldTypeRequiredDescription
namestringYesWorkflow name
descriptionstringNoHuman-readable description
triggerobjectYesHow the workflow is triggered
nodesarrayYesWorkflow nodes (steps)
edgesarrayYesConnections between nodes
entryNodeIdstringYesStarting node ID
enabledbooleanNoActive state (default: true)
personaIdstringNoLimit to specific persona
timeoutnumberNoMax 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

FieldTypeDescription
statusstringFilter: active, error, disabled
triggerstringFilter by trigger type
limitnumberMax results (default: 50)
offsetnumberPagination 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

FieldTypeDescription
statusstringFilter: running, completed, failed, cancelled
limitnumberMax 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-3.5-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:

ReferenceExample
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