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

# Flows Overview

> List, read, and control MoEngage Flows.

Use the Flows endpoints to discover flows, view a single flow (including its versions and stages), and change a flow's status. These endpoints read and control flows; they do not create them.

<Note>
  The Flows endpoints are in early access. MoEngage enhances these endpoints based on feedback during this period, but does not make breaking changes.
</Note>

## Endpoints

The Flows API is a collection of the following endpoints:

* [Search Flows](/docs/api/flows/search-flows): Lists flows with filters and keyset pagination.
* [Get a Single Flow](/docs/api/flows/get-a-single-flow): Returns a single flow, and optionally a specific version with `version_no`.
* [Get a Specific Version of a Flow](/docs/api/flows/get-a-specific-version-of-a-flow): Returns a flow as it was in a specific version, addressed by its version ID.
* [Update Flow Status](/docs/api/flows/update-flow-status): Pauses, resumes, stops, retires, archives, or unarchives a flow.

## Authentication

Authenticate with Basic Auth using your MoEngage Workspace ID (App ID) as the username and your API Key as the password. You can find these in the MoEngage dashboard at **Settings** > **Account** > **APIs**.

Flows reuse the Campaigns permissions. Enable the matching checkboxes on your API key:

* **View** (`campaigns:view`) for the read endpoints — Search Flows, Get a Single Flow, and Get a Specific Version of a Flow.
* **Create & Manage** (`campaigns:create_manage`) for Update Flow Status.

The `X-MOE-Tenant-ID` header (your Workspace ID) is optional — when omitted, the workspace is resolved from the Basic Auth credentials in the `Authorization` header. You can also send an optional `X-MOE-Request-Id`.

For more information on authentication and getting your credentials, refer to [Getting your credentials](https://www.moengage.com/docs/api/introduction#getting-your-credentials).

Requests go to your data center's host, `https://api-{dc}.moengage.com`. For the full list of data centers and their identifiers, refer to [Data Centers](https://www.moengage.com/docs/api/introduction#data-centers-and-endpoints).

## Conventions

* IDs are raw 24-character ObjectIds. `flow_id` addresses the flow; `version_id` addresses one version.
* Every successful response uses the envelope `{response_id, type, data}`.
* Datetimes are ISO 8601 UTC (`YYYY-MM-DDThh:mm:ssZ`).
* Search results are paginated. Each response includes a `next_cursor`. To get the next page, send that value as the `cursor` parameter in your next request.

## Flow Structure and Nodes

**Get a single flow** and **Get a specific version of a flow** return the flow's stages in `data.structure.nodes[]`. Each entry is a node (one stage of the flow). Nodes share a common envelope and carry a `config` object whose shape depends on the node's `type` and `sub_type`.

Every node includes these fields:

| Field             | Description                                                                       |
| ----------------- | --------------------------------------------------------------------------------- |
| `stage_id`        | Unique ID of the node within the flow.                                            |
| `type`            | Node category: `TRIGGER`, `ACTION`, `CONDITION`, `SPLIT`, `BRANCH`, or `CONTROL`. |
| `sub_type`        | Specific variant within the `type`.                                               |
| `parent_stage_id` | ID of the upstream node, or `null` for the entry (`TRIGGER`) node.                |
| `child_stage_ids` | IDs of the immediate downstream nodes.                                            |
| `label`           | Human-readable label from the flow builder. May be `null`.                        |
| `config`          | Type-specific configuration. Null-valued fields are omitted.                      |

The sections below describe the `config` shape for each node type. Field sets are documented from real flows; a node may include additional keys not shown here.

### Condition Nodes

A `CONDITION` node evaluates a condition and routes users down one of two paths. `child_stage_ids` always has exactly two entries: index `0` is the path taken when the condition matches, and index `1` is the path when it does not.

`config` fields:

* `condition` — the rule to evaluate. It reuses the same filter grammar as a trigger's event filter (`filter_operator`, `filters[]`, and per-filter `action_name`, `executed`, `execution.type`/`execution.count`, and nested `attributes`).
* `wait` — how long to wait before evaluating, as `{ duration, unit }`. `unit` is `mins`, `hours`, or `days`.
* `evaluation_timing` — when the condition is evaluated: `ON_ENTRY` or `SINCE_PREVIOUS_STAGE`.

Observed `sub_type` values: `HAS_DONE_EVENT`, `ON_EMAIL_CLICK`, `ON_EMAIL_OPEN`.

```json Two-way CONDITION node theme={null}
{
  "stage_id": "Y8nSUSRe6",
  "type": "CONDITION",
  "sub_type": "HAS_DONE_EVENT",
  "parent_stage_id": "ZL4rtyzrd",
  "child_stage_ids": ["2iuY5k_zy", "TOGBwROjc7"],
  "label": "Email Clicked",
  "config": {
    "condition": {
      "filter_operator": "and",
      "filters": [
        {
          "action_name": "MOE_EMAIL_CLICK",
          "executed": true,
          "filter_type": "actions",
          "execution": { "count": 1, "type": "atleast" },
          "attributes": { "filter_operator": "and", "filters": [] }
        }
      ]
    },
    "wait": { "duration": 5, "unit": "mins" },
    "evaluation_timing": "SINCE_PREVIOUS_STAGE"
  }
}
```

### Branch Nodes

A `BRANCH` node represents a single arm of a parent `CONDITION` or `SPLIT`. Its `sub_type` mirrors the parent's `sub_type`, and its single child is the next real stage on that path. Its `config` depends on the parent:

* Arm of a `CONDITION` (explicit YES/NO pattern): `config` is `{ "outcome": "YES" }` or `{ "outcome": "NO" }`.
* Arm of a `CONDITIONAL_SPLIT`: `config` carries `branch_index` (0-based) and, for a conditioned arm, `branch_type: "main"` with a `condition` object. The catch-all arm uses `branch_type: "default"` and has no `condition`.
* Arm of an `AB_SPLIT` or `IPO`: `config` carries `name` and `percentage`.

```json BRANCH arms of a CONDITIONAL_SPLIT theme={null}
{
  "stage_id": "KFs3PrkZJ",
  "type": "BRANCH",
  "sub_type": "CONDITIONAL_SPLIT",
  "parent_stage_id": "f3OFPd5GNe",
  "child_stage_ids": ["dJ0FjUCcC"],
  "label": "Signed up and Purchased",
  "config": {
    "branch_type": "main",
    "branch_index": 0,
    "condition": { "filter_operator": "and", "filters": [] }
  }
}
```

### Split Nodes

A `SPLIT` node splits users into more than two paths (or two paths for `IPO`). Its `child_stage_ids` map one-to-one to `BRANCH` children. `config` fields depend on the `sub_type`:

* `CONDITIONAL_SPLIT` routes each user down a branch based on a condition. Its `config` carries `branch_order` (the ordered list of child `stage_id`s), `evaluation_timing`, and `wait`.
* `AB_SPLIT` divides users randomly by percentage. Its `config.branches[]` lists each arm as `{ stage_id, name, percentage }`.
* `IPO` (Intelligent Path Optimizer) uses the same `config.branches[]` as `AB_SPLIT`, plus an `optimization_metric` (observed value: `ENGAGEMENT`). The ordering comes from `branches[]`, so there is no `branch_order`.

```json Multi-way CONDITIONAL_SPLIT theme={null}
{
  "stage_id": "f3OFPd5GNe",
  "type": "SPLIT",
  "sub_type": "CONDITIONAL_SPLIT",
  "child_stage_ids": ["KFs3PrkZJ", "CfwMAJthB", "EfjrVVREJ9"],
  "label": "Has Signed Up and Purchased a Policy?",
  "config": {
    "evaluation_timing": "ON_ENTRY",
    "branch_order": ["KFs3PrkZJ", "CfwMAJthB", "EfjrVVREJ9"],
    "wait": { "duration": 0, "unit": "hours" }
  }
}
```

```json AB_SPLIT theme={null}
{
  "stage_id": "uMQdmCeJia",
  "type": "SPLIT",
  "sub_type": "AB_SPLIT",
  "parent_stage_id": "CfwMAJthB",
  "child_stage_ids": ["LsyB1exIx", "eElEw9Uaq8", "fxHKPCJ-M"],
  "label": "A/B Split",
  "config": {
    "branches": [
      { "stage_id": "LsyB1exIx", "name": "Branch 1", "percentage": 33 },
      { "stage_id": "eElEw9Uaq8", "name": "Branch 2", "percentage": 33 },
      { "stage_id": "fxHKPCJ-M", "name": "Branch 3", "percentage": 34 }
    ]
  }
}
```

### Action Nodes

An `ACTION` node sends a campaign or selects a channel. For a channel send (`EMAIL`, `PUSH`, `SMS`, `WHATSAPP`, and so on), `config` carries `campaign_id`, `channel`, and `campaign_name`.

The `NBA` (Next Best Action) sub\_type is an `ACTION` node — it occupies the same slot as channel sends, not a separate node type. Its `config` fields:

* `channels[]` — one entry per eligible channel, each `{ channel, stage_id, campaign_id }`. `stage_id` references a sibling `ACTION` node (of that channel) that owns the actual send; the NBA node itself holds no campaign content.
* `fallback_channel` — the channel used when the best channel can't be determined (observed value: `PUSH`).
* `best_time_to_send` — the send-time strategy (observed shape: `{ "default_time": "SEND_IMMEDIATELY" }`).

```json ACTION node with NBA sub_type theme={null}
{
  "stage_id": "0-5G1JPQX",
  "type": "ACTION",
  "sub_type": "NBA",
  "parent_stage_id": "abw_MALGPa",
  "child_stage_ids": ["dgVoYPiUPT"],
  "label": "Next Best Action",
  "config": {
    "channels": [
      { "channel": "PUSH", "stage_id": "x_-NueaX4X", "campaign_id": "69844f63316edd32518c5aef" },
      { "channel": "EMAIL", "stage_id": "zN9ibHPXbE", "campaign_id": "69844f63316edd32518c5af1" }
    ],
    "fallback_channel": "PUSH",
    "best_time_to_send": { "default_time": "SEND_IMMEDIATELY" }
  }
}
```

### Control Nodes

A `CONTROL` node manages flow control and path convergence. Observed `sub_type` values:

* `EXIT` — exits the user from the flow.
* `WAIT_FOR_TIMER` — holds the user for a set duration.
* `GO_TO` — routes the user to a shared downstream stage (`config.target_stage_id`), used to re-converge parallel branches.

## FAQs

<AccordionGroup>
  <Accordion title="Can I create or edit flows with these endpoints?">
    No, this isn't supported. You can search, read, and change the status of existing flows.
  </Accordion>

  <Accordion title="How do I enroll users into a flow through the API?">
    You do not enroll users directly. For event-triggered flows, use the Track Event API — a user enters the flow when a tracked event matches the flow's entry condition. For business-event-triggered flows, use the Trigger Business Event API. Periodic flows are audience-driven and do not support API enrollment.
  </Accordion>

  <Accordion title="What is the difference between flow_id and version_id?">
    `flow_id` identifies the flow. `version_id` identifies one of its versions. To read a specific version, either pass `version_no` on **Get a single flow**, or call **Get a specific version of a flow** with the `version_id`.
  </Accordion>

  <Accordion title="Which status changes can I make?">
    You can `pause`, `resume`, `stop`, `retire`, `archive`, and `unarchive` a flow. Each change is validated against the flow's current status; an invalid change returns a `409`. Publish isn't available.
  </Accordion>

  <Accordion title="What permissions do I need?">
    The read endpoints (search, get flow, get version) require the `campaigns:view` permission. The status endpoint requires the `campaigns:create_manage` permission.
  </Accordion>
</AccordionGroup>
