> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Flows API

> Create, manage, and monitor automation workflows and their executions

# Flows API

Build visual automation workflows that trigger on events (incoming message, webhook, schedule) and execute multi-step logic — send messages, call APIs, branch on conditions, invoke AI agents, and more.

**Base path:** `/api/v1/flows`

***

## Create Flow

`POST /api/v1/flows`

Create a new automation flow.

<ParamField body="name" type="string" required>
  Flow name
</ParamField>

<ParamField body="trigger_type" type="string" required>
  What starts this flow: `manual`, `webhook`, `schedule`, `event`, `workflow`
</ParamField>

<ParamField body="cron_expr" type="string">
  Cron schedule that fires the flow. **Required when `trigger_type` is `schedule`** — a schedule flow submitted without it returns `422 VALIDATION_ERROR`. Accepts a standard 5-field cron expression (`0 9 * * 1` runs every Monday at 09:00) or a predefined alias such as `@hourly`, `@daily`, or `@weekly`. Ignored for every other trigger type.
</ParamField>

<ParamField body="cron_tz" type="string" default="UTC">
  IANA timezone the `cron_expr` is evaluated in, for example `America/New_York`. Defaults to `UTC`. An unrecognized timezone returns `422 VALIDATION_ERROR`.
</ParamField>

<ParamField body="definition" type="object" required>
  Flow definition — a JSON object representing the node graph from the visual builder. Contains nodes, edges, and configuration for each step.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://api.orbit.devotel.io/api/v1/flows" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
      "name": "Welcome Flow",
      "trigger_type": "event",
      "definition": {
        "nodes": [
          {
            "id": "trigger",
            "type": "trigger",
            "data": {
              "channel": "whatsapp"
            }
          },
          {
            "id": "reply",
            "type": "sendWhatsapp",
            "data": {
              "body": "Welcome! How can we help?"
            }
          }
        ],
        "edges": [
          {
            "source": "trigger",
            "target": "reply"
          }
        ]
      }
    }'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://api.orbit.devotel.io/api/v1/flows', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
      "name": "Welcome Flow",
      "trigger_type": "event",
      "definition": {
        "nodes": [
          {
            "id": "trigger",
            "type": "trigger",
            "data": {
              "channel": "whatsapp"
            }
          },
          {
            "id": "reply",
            "type": "sendWhatsapp",
            "data": {
              "body": "Welcome! How can we help?"
            }
          }
        ],
        "edges": [
          {
            "source": "trigger",
            "target": "reply"
          }
        ]
      }
    }),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://api.orbit.devotel.io/api/v1/flows", headers=headers, json={
      "name": "Welcome Flow",
      "trigger_type": "event",
      "definition": {
        "nodes": [
          {
            "id": "trigger",
            "type": "trigger",
            "data": {
              "channel": "whatsapp"
            }
          },
          {
            "id": "reply",
            "type": "sendWhatsapp",
            "data": {
              "body": "Welcome! How can we help?"
            }
          }
        ],
        "edges": [
          {
            "source": "trigger",
            "target": "reply"
          }
        ]
      }
    })
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/flows", bytes.NewBuffer([]byte(`{
      "name": "Welcome Flow",
      "trigger_type": "event",
      "definition": {
        "nodes": [
          {
            "id": "trigger",
            "type": "trigger",
            "data": {
              "channel": "whatsapp"
            }
          },
          {
            "id": "reply",
            "type": "sendWhatsapp",
            "data": {
              "body": "Welcome! How can we help?"
            }
          }
        ],
        "edges": [
          {
            "source": "trigger",
            "target": "reply"
          }
        ]
      }
    }`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://api.orbit.devotel.io/api/v1/flows')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {
      "name": "Welcome Flow",
      "trigger_type": "event",
      "definition": {
        "nodes": [
          {
            "id": "trigger",
            "type": "trigger",
            "data": {
              "channel": "whatsapp"
            }
          },
          {
            "id": "reply",
            "type": "sendWhatsapp",
            "data": {
              "body": "Welcome! How can we help?"
            }
          }
        ],
        "edges": [
          {
            "source": "trigger",
            "target": "reply"
          }
        ]
      }
    }.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://api.orbit.devotel.io/api/v1/flows');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {
      "name": "Welcome Flow",
      "trigger_type": "event",
      "definition": {
        "nodes": [
          {
            "id": "trigger",
            "type": "trigger",
            "data": {
              "channel": "whatsapp"
            }
          },
          {
            "id": "reply",
            "type": "sendWhatsapp",
            "data": {
              "body": "Welcome! How can we help?"
            }
          }
        ],
        "edges": [
          {
            "source": "trigger",
            "target": "reply"
          }
        ]
      }
    }
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 201 theme={null}
  {
    "data": {
      "id": "flw_abc123",
      "name": "Welcome Flow",
      "trigger_type": "event",
      "status": "draft",
      "version": 1,
      "created_at": "2026-03-08T12:00:00Z"
    },
    "meta": {
      "request_id": "req_flw_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

### Scheduled flows

For a `schedule` trigger, include `cron_expr` (and optionally `cron_tz`) alongside the definition:

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/flows" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Daily Cart Reminder",
  "trigger_type": "schedule",
  "cron_expr": "0 9 * * *",
  "cron_tz": "America/New_York",
  "definition": { "nodes": [], "edges": [] }
}'
```

***

## List Flows

`GET /api/v1/flows`

Retrieve all flows with cursor-based pagination.

<ParamField query="cursor" type="string">
  Cursor for pagination
</ParamField>

<ParamField query="limit" type="integer" default="25">
  Number of results per page (max 200)
</ParamField>

***

## Get Flow

`GET /api/v1/flows/{id}`

Retrieve a flow by ID, including its full definition.

<ParamField path="id" type="string" required>
  Flow ID (e.g., `flw_abc123`)
</ParamField>

***

## Validate Flow

`GET /api/v1/flows/{id}/validate`

Check a flow's definition for structural problems before publishing: a missing trigger node, disconnected nodes, nodes without an `id` or `type`, misweighted A/B split variants, and infinite loops (a cycle with no delay or wait node). Returns a list of problems — an empty `errors` list means the flow is publishable. Read-only.

<ParamField path="id" type="string" required>
  Flow ID
</ParamField>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "valid": false,
      "errors": [
        "Flow must have at least one trigger node",
        "Node 'sendSms-1' is disconnected"
      ]
    },
    "meta": {
      "request_id": "req_flow_validate_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

***

## Update Flow

`PUT /api/v1/flows/{id}`

Update a flow's name, trigger, or definition. Each update increments the version number.

<ParamField path="id" type="string" required>
  Flow ID
</ParamField>

<ParamField body="name" type="string">
  Updated flow name
</ParamField>

<ParamField body="trigger_type" type="string">
  Updated trigger type
</ParamField>

<ParamField body="cron_expr" type="string">
  Cron schedule for a `schedule` trigger. Required whenever the flow's effective `trigger_type` is `schedule` — you can update the schedule on an existing schedule flow by sending `cron_expr` alone. An empty or unparseable expression returns `422 VALIDATION_ERROR`.
</ParamField>

<ParamField body="cron_tz" type="string" default="UTC">
  IANA timezone the `cron_expr` is evaluated in. Defaults to `UTC`.
</ParamField>

<ParamField body="definition" type="object">
  Updated flow definition (node graph)
</ParamField>

<ParamField body="version" type="integer">
  Expected current version (for optimistic concurrency control). The update is rejected if the stored version does not match.
</ParamField>

***

## Delete Flow

`DELETE /api/v1/flows/{id}`

Delete a flow. Running executions are allowed to complete but no new executions will start.

<ParamField path="id" type="string" required>
  Flow ID
</ParamField>

**Response:** `204 No Content`

***

## Publish Flow

`POST /api/v1/flows/{id}/publish`

Validate and publish a flow. The current definition is validated (the same checks as [Validate Flow](#validate-flow)), frozen as an immutable version snapshot, and the flow's status is set to `published`. Publishing a flow that fails validation returns `422 VALIDATION_ERROR` with the list of problems.

<ParamField path="id" type="string" required>
  Flow ID
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST "https://api.orbit.devotel.io/api/v1/flows/flw_abc123/publish" \
    -H "X-API-Key: dv_live_sk_your_key_here"
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "id": "flw_abc123",
      "name": "Welcome Flow",
      "status": "published",
      "version": 2
    },
    "meta": {
      "request_id": "req_flow_publish_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

***

## Unpublish Flow

`POST /api/v1/flows/{id}/unpublish`

Take a published flow out of live execution and return it to `draft`. Only a `published` flow can be unpublished — calling this on a draft or archived flow returns `422 VALIDATION_ERROR`.

<ParamField path="id" type="string" required>
  Flow ID
</ParamField>

***

## Execute Flow

`POST /api/v1/flows/{id}/execute`

Run a flow immediately with an explicit trigger payload. The payload is available to every node as `trigger_data`. Returns `202 Accepted` with the execution record. Concurrent executions are capped per workspace; exceeding the cap returns `429`. Rate limited to 10 requests per minute.

<ParamField path="id" type="string" required>
  Flow ID
</ParamField>

<ParamField body="trigger_data" type="object" default="{}">
  Arbitrary key/value payload passed to the flow's nodes as `trigger_data`.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST "https://api.orbit.devotel.io/api/v1/flows/flw_abc123/execute" \
    -H "X-API-Key: dv_live_sk_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{ "trigger_data": { "phone": "+15551234567", "otp": "123456" } }'
  ```
</RequestExample>

<ResponseExample>
  ```json 202 theme={null}
  {
    "data": {
      "id": "exec_010",
      "flow_id": "flw_abc123",
      "status": "running",
      "started_at": "2026-03-08T12:00:00Z"
    },
    "meta": {
      "request_id": "req_flow_execute_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

***

## Enroll Contacts in a Flow

`POST /api/v1/flows/{id}/start`

Enroll a single contact or an entire list into a flow — the high-level enrollment entry point for programmatic and agent-driven use. Pass exactly one of `contact_id` or `list_id`, plus an optional `context` object that is merged into each enrollment's `trigger_data`. Returns `202 Accepted`. Rate limited to 10 requests per minute.

<ParamField path="id" type="string" required>
  Flow ID
</ParamField>

<ParamField body="contact_id" type="string">
  Enroll a single contact. Returns `{ "mode": "single", "execution": { ... } }`.
</ParamField>

<ParamField body="list_id" type="string">
  Enroll every member of a contact list (up to 500 members). Returns `{ "mode": "list", "attempted": N, "enrolled": M }`. Lists larger than 500 members return `413` — use bulk enrollment for large audiences instead.
</ParamField>

<ParamField body="context" type="object">
  Optional key/value data merged into each enrollment's `trigger_data`.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST "https://api.orbit.devotel.io/api/v1/flows/flw_abc123/start" \
    -H "X-API-Key: dv_live_sk_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{ "contact_id": "con_789", "context": { "plan": "pro" } }'
  ```
</RequestExample>

<ResponseExample>
  ```json 202 theme={null}
  {
    "data": {
      "mode": "single",
      "execution": {
        "id": "exec_011",
        "flow_id": "flw_abc123",
        "status": "running"
      }
    },
    "meta": {
      "request_id": "req_flow_start_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

***

## List Flow Versions

`GET /api/v1/flows/{id}/versions`

List the saved version snapshots for a flow, newest first. Each publish writes a snapshot, and operators can also save manual snapshots from the builder.

<ParamField path="id" type="string" required>
  Flow ID
</ParamField>

***

## Create Flow Version

`POST /api/v1/flows/{id}/versions`

Save a manual snapshot of the flow's current definition. With an empty body the server snapshots the flow's current `definition`. Returns `201 Created` with the new version.

<ParamField path="id" type="string" required>
  Flow ID
</ParamField>

<ParamField body="definition" type="object">
  Definition to snapshot. Defaults to the flow's current stored definition.
</ParamField>

<ParamField body="diff_summary" type="string">
  Optional short description of what changed in this version.
</ParamField>

***

## Restore Flow Version

`POST /api/v1/flows/{id}/versions/{versionId}/restore`

Restore an earlier snapshot onto the flow's draft definition. The flow's status is unchanged — a published flow keeps serving its published version until you publish the restored draft.

<ParamField path="id" type="string" required>
  Flow ID
</ParamField>

<ParamField path="versionId" type="string" required>
  Version snapshot ID to restore.
</ParamField>

***

## Export Flow

`GET /api/v1/flows/{id}/export`

Export a flow as portable, vendor-neutral JSON. The document captures the flow definition, its trigger and schedule, and — unless `include_agents=false` — the full configuration of every AI agent the flow references, so you can move your work to another platform or keep an offline backup. The response body is the export document itself, with no envelope.

<ParamField path="id" type="string" required>
  Flow ID
</ParamField>

<ParamField query="include_agents" type="boolean" default="true">
  Inline the configuration of every agent the flow references. Set to `false` to export only the flow definition.
</ParamField>

<ParamField query="download" type="boolean" default="false">
  When `true`, sets a `Content-Disposition` header so a browser downloads the export as a file.
</ParamField>

<ResponseExample>
  ```json 200 theme={null}
  {
    "schema_version": "1.0.0",
    "kind": "orbit.flow.v1",
    "exported_at": "2026-03-08T12:00:00Z",
    "source": { "platform": "orbit-cpaas", "type": "flow" },
    "flow": {
      "id": "flw_abc123",
      "name": "Welcome Flow",
      "description": null,
      "trigger_type": "event",
      "cron_expr": null,
      "cron_tz": null,
      "status": "published",
      "version": 2,
      "definition": { "nodes": [], "edges": [] }
    },
    "agents": []
  }
  ```
</ResponseExample>

***

## List Flow Templates

`GET /api/v1/flows/templates`

Return the built-in starter templates shown in the flow builder's "New Flow" dialog — welcome messages, OTP verification, abandoned-cart reminders, lead nurture, support escalation, and more. Each template includes its trigger type, channels, and a ready-to-use `definition` you can pass straight to [Create Flow](#create-flow).

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": [
      {
        "id": "tpl_welcome_sms",
        "name": "Welcome SMS",
        "description": "Send a welcome SMS when a new contact is created.",
        "trigger_type": "event",
        "category": "welcome",
        "channels": ["SMS"],
        "nodeCount": 2,
        "definition": { "nodes": [], "edges": [] }
      }
    ],
    "meta": {
      "request_id": "req_flow_templates_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

***

## List Executions

`GET /api/v1/flows/executions`

Retrieve flow execution logs across all flows, ordered by most recent.

<ParamField query="cursor" type="string">
  Cursor for pagination
</ParamField>

<ParamField query="limit" type="integer" default="25">
  Number of results per page (max 200)
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://api.orbit.devotel.io/api/v1/flows/executions?limit=10" \
      -H "X-API-Key: dv_live_sk_your_key_here" 
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://api.orbit.devotel.io/api/v1/flows/executions?limit=10', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())


    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://api.orbit.devotel.io/api/v1/flows/executions?limit=10", headers=headers)
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/flows/executions?limit=10", nil)
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))

    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://api.orbit.devotel.io/api/v1/flows/executions?limit=10')
    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']


    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://api.orbit.devotel.io/api/v1/flows/executions?limit=10');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),

    ]);

    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": [
      {
        "id": "exec_001",
        "flow_id": "flw_abc123",
        "flow_name": "Welcome Flow",
        "trigger_type": "event",
        "status": "completed",
        "steps_executed": 3,
        "started_at": "2026-03-08T11:00:00Z",
        "completed_at": "2026-03-08T11:00:02Z",
        "duration_ms": 2100
      },
      {
        "id": "exec_002",
        "flow_id": "flw_def456",
        "flow_name": "Order Notification",
        "trigger_type": "webhook",
        "status": "failed",
        "steps_executed": 2,
        "error": "Timeout waiting for external API response",
        "started_at": "2026-03-08T10:30:00Z",
        "completed_at": "2026-03-08T10:30:31Z",
        "duration_ms": 31000
      }
    ],
    "meta": {
      "request_id": "req_exec_001",
      "timestamp": "2026-03-08T12:00:00Z",
      "pagination": {
        "cursor": "cur_exec_abc",
        "has_more": true
      }
    }
  }
  ```
</ResponseExample>

***

## Get Execution

`GET /api/v1/flows/executions/{id}`

Retrieve a single flow execution by ID, including its full trigger payload and step-by-step trace — each node's name, status, duration, and any error. Returns `404` if the execution does not exist.

<ParamField path="id" type="string" required>
  Execution ID (e.g., `exec_001`)
</ParamField>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "id": "exec_001",
      "flow_id": "flw_abc123",
      "status": "completed",
      "trigger_data": { "phone": "+15551234567" },
      "steps": [
        {
          "name": "Trigger",
          "status": "completed",
          "duration_ms": 4
        },
        {
          "name": "Send Welcome SMS",
          "status": "completed",
          "duration_ms": 210
        }
      ],
      "started_at": "2026-03-08T11:00:00Z",
      "completed_at": "2026-03-08T11:00:02Z"
    },
    "meta": {
      "request_id": "req_flow_execution_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

***

## Get Executions Summary

`GET /api/v1/flows/executions/summary`

Aggregate execution statistics over a rolling `started_at` window — total count, a completed/failed/running breakdown, and average duration. Powers the flow-executions dashboard stat cards so totals stay accurate across every execution, not just the currently loaded page. Read-only.

<ParamField query="flow_id" type="string">
  Restrict the aggregate to a single flow.
</ParamField>

<ParamField query="status" type="string">
  Restrict the aggregate to executions in this status: `running`, `waiting`, `completed`, `completed_with_errors`, `failed`, `timeout`.
</ParamField>

<ParamField query="window_days" type="integer" default="30">
  Rolling `started_at` window in days, clamped to `[1, 365]`. Pass `1` for a last-24h ("today") count.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://api.orbit.devotel.io/api/v1/flows/executions/summary?window_days=7" \
      -H "X-API-Key: dv_live_sk_your_key_here"
    ```

    ```typescript Node.js theme={null}
    const res = await fetch('https://api.orbit.devotel.io/api/v1/flows/executions/summary?window_days=7', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://api.orbit.devotel.io/api/v1/flows/executions/summary?window_days=7", headers=headers)
    print(r.json())
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "total": 1284,
      "completed": 1190,
      "failed": 47,
      "running": 12,
      "avg_duration_ms": 2143
    },
    "meta": {
      "request_id": "req_exec_summary_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

***

## Flow Analytics

`GET /api/v1/flows/{id}/analytics`

Per-flow conversion funnel: how many contacts entered and completed the flow, a per-node drop-off funnel in flow-graph traversal order, and goal attribution (event or trait) within the configured window after a contact exits the flow. The conversion goal defaults to the flow's stored `definition.conversionGoal` and can be overridden per request. Read-only aggregation over the executions log.

<ParamField path="id" type="string" required>
  Flow ID (e.g., `flw_abc123`)
</ParamField>

<ParamField query="since" type="string">
  Lower bound (inclusive) of the entry window — executions started at or after this ISO-8601 timestamp.
</ParamField>

<ParamField query="until" type="string">
  Upper bound of the entry window — executions started before this ISO-8601 timestamp.
</ParamField>

<ParamField query="window_days" type="integer">
  Goal attribution window in days after a contact exits the flow, clamped to `[1, 365]`.
</ParamField>

<ParamField query="goal_event" type="string">
  Override the flow's stored goal — count this event as the conversion.
</ParamField>

<ParamField query="goal_trait" type="string">
  Override the flow's stored goal — count an identify call setting this trait as the conversion.
</ParamField>

<ParamField query="goal_value" type="string">
  When `goal_trait` is set, only count the conversion when the trait is set to this value.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://api.orbit.devotel.io/api/v1/flows/flw_abc123/analytics?window_days=7" \
      -H "X-API-Key: dv_live_sk_your_key_here"
    ```

    ```typescript Node.js theme={null}
    const res = await fetch('https://api.orbit.devotel.io/api/v1/flows/flw_abc123/analytics?window_days=7', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://api.orbit.devotel.io/api/v1/flows/flw_abc123/analytics?window_days=7", headers=headers)
    print(r.json())
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "flow_id": "flw_abc123",
      "since": "2026-03-01T00:00:00Z",
      "until": "2026-03-08T00:00:00Z",
      "window_days": 7,
      "goal": {
        "type": "event",
        "event": "purchase_completed"
      },
      "entered": 500,
      "completed": 410,
      "converted": 120,
      "completion_rate": 0.82,
      "conversion_rate": 0.24,
      "funnel": [
        {
          "node_id": "trigger",
          "node_type": "trigger",
          "label": "Inbound WhatsApp",
          "reached": 500,
          "dropped_off": 0,
          "step_conversion_rate": null,
          "overall_conversion_rate": null
        },
        {
          "node_id": "reply",
          "node_type": "sendWhatsapp",
          "label": "Welcome message",
          "reached": 460,
          "dropped_off": 40,
          "step_conversion_rate": 0.92,
          "overall_conversion_rate": 0.92
        }
      ]
    },
    "meta": {
      "request_id": "req_flow_analytics_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

***

## A/B Test Results

`GET /api/v1/flows/{id}/ab-results`

Per-variant results for every in-flow A/B test (`abTest`) node: how many contacts were assigned to each variant, how many completed and converted, the conversion (or completion) rate per variant, and the leading variant. Accepts the same goal and window overrides as [Flow Analytics](#flow-analytics). Read-only.

<ParamField path="id" type="string" required>
  Flow ID (e.g., `flw_abc123`)
</ParamField>

<ParamField query="since" type="string">
  Lower bound (inclusive) of the entry window — executions started at or after this ISO-8601 timestamp.
</ParamField>

<ParamField query="until" type="string">
  Upper bound of the entry window — executions started before this ISO-8601 timestamp.
</ParamField>

<ParamField query="window_days" type="integer">
  Goal attribution window in days after a contact exits the flow, clamped to `[1, 365]`.
</ParamField>

<ParamField query="goal_event" type="string">
  Override the flow's stored goal — count this event as the conversion.
</ParamField>

<ParamField query="goal_trait" type="string">
  Override the flow's stored goal — count an identify call setting this trait as the conversion.
</ParamField>

<ParamField query="goal_value" type="string">
  When `goal_trait` is set, only count the conversion when the trait is set to this value.
</ParamField>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "flow_id": "flw_abc123",
      "tests": [
        {
          "node_id": "abTest-1",
          "leading_variant": "b",
          "variants": [
            {
              "variant": "a",
              "assigned": 240,
              "completed": 198,
              "converted": 52,
              "conversion_rate": 0.22
            },
            {
              "variant": "b",
              "assigned": 236,
              "completed": 205,
              "converted": 71,
              "conversion_rate": 0.30
            }
          ]
        }
      ]
    },
    "meta": {
      "request_id": "req_flow_ab_results_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>
