# Add a router branch

Appends one branch to a router step.

- Endpoint: `POST https://api.returning.ai/v1/workflow-builder/data-workflows/{workflowId}/nodes/{nodeId}/branches`
- Section: Workflow Builder / Router branches
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `workflowBuilder`
- Retries: Not idempotent: two accepted calls create two branches with the same name.
- Guide: generated from the published specification
- Last updated: 28 Oct 2026
- Web page: https://docs.returning.ai/api-reference/workflow-builder/add-a-router-branch

## Authentication

- Header: `Authorization: Bearer <API_KEY>`
- Permission: `workflowBuilder`

`Authorization: Bearer <API_KEY>` with `workflowBuilder`, on a workflow of the key's community. `If-Match` is optional. Send the `revision` you read to avoid overwriting a concurrent change (a stale value is refused with `409 VERSION_CONFLICT`); omit it and your write wins as the last one. A deployed workflow is edited with the same call: the change applies immediately.

| Failure | Meaning | Recovery |
| --- | --- | --- |
| `400 VALIDATION_FAILED` | The step is not a router, `branchName` is empty, or `branchType` is neither `CONDITION` nor `FALLBACK` | Send a branch for a router step, with both required fields. |

## Behaviour

A branch is a condition group plus the steps that run when it matches, or a fallback that runs when nothing else did.

The new branch is appended at the end of the router's branch list, and its step list starts empty. Steps are added to it with `POST /{workflowId}/nodes` and `position: {routerNodeId, branchIndex}`.

## Request

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `workflowId` | `string` | Yes | Workflow identifier: 24 hexadecimal characters, as returned in `_id`. (`^[0-9a-fA-F]{24}$`) |
| `nodeId` | `string` | Yes | Step identifier: 24 hexadecimal characters, as returned in the step's `_id`. (`^[0-9a-fA-F]{24}$`) |

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | `string` | Yes | Community API key: `Bearer <API_KEY>`. |
| `If-Match` | `string` | No | Optional. Send the `revision` the change is based on, bare or quoted, to avoid overwriting a concurrent change: a stale value is refused with `409 VERSION_CONFLICT`. Omit it for last-write-wins. |
| `Content-Type` | `string` | Yes | Request body format. (`application/json`) |

### Body

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `branchName` | `string` | Yes | Branch name shown on the canvas. (1-200 chars) |
| `branchType` | `string` | Yes | `CONDITION` for a rule, `FALLBACK` for the default path. (`CONDITION`, `FALLBACK`) |
| `conditions` | `object[]` | No | Condition groups. Prefer `AND`: the runtime treats `OR` and `WHERE` groups as always true, and validate reports `ROUTER_OPERATOR_UNSAFE` as a warning. Send `[]` for a fallback. |

### Watch for

- Conditions use `AND` groups. A group with `OR` or `WHERE` is stored, and `POST /{workflowId}/validate` reports `ROUTER_OPERATOR_UNSAFE` as a warning: the runtime treats those groups as always true, so the branch fires more often than you meant. Deploy still proceeds, so use `AND` groups and read the warnings.
- Each condition needs an `id`, a `key`, a `type`, an `operator` and a `value`, all as non-empty arrays for `key` and `value`.
- A branch's steps are named in `workflowNodeIds`. A branch that names a step of another workflow is `400 NODE_NOT_IN_WORKFLOW`: that is a tenant check, not a validation warning.
- A router needs at least one branch to be runnable; `POST /{workflowId}/validate` reports `BRANCH_REQUIRED` as a warning when it has none, and deploy still proceeds. `executePaths` decides whether the first match wins or every match runs; a fallback only runs when no condition matched.
- Deleting the last branch is refused, so build the replacement before removing the old one.

### Example request

```bash
curl --request POST \
  --url 'https://api.returning.ai/v1/workflow-builder/data-workflows/<workflowId>/nodes/<nodeId>/branches' \
  --header 'Authorization: Bearer <API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "branchName": "Coins > 500",
    "branchType": "CONDITION",
    "conditions": [
      {
        "id": "group-2",
        "operator": "AND",
        "nestedGroups": [
          {
            "id": "nested-2",
            "operator": "AND",
            "conditions": [
              {
                "id": "condition-2",
                "key": ["{{65f1a2b3c4d5e6f708192a3c.Total Coins}}"],
                "type": "number",
                "operator": "greater_than",
                "value": ["500"]
              }
            ]
          }
        ]
      }
    ]
  }'
```

### More examples

A condition branch:

```bash
curl --request POST \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes/65f1a2b3c4d5e6f708192a3e/branches" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --header "If-Match: 162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c" \
  --data '{"branchName":"Coins > 500","branchType":"CONDITION","conditions":[{"id":"group-2","operator":"AND","nestedGroups":[{"id":"nested-2","operator":"AND","conditions":[{"id":"condition-2","key":["{{65f1a2b3c4d5e6f708192a3c.Total Coins}}"],"type":"number","operator":"greater_than","value":["500"]}]}]}]}'
```

A fallback branch, with an empty `conditions` array:

```bash
curl --request POST \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes/65f1a2b3c4d5e6f708192a3e/branches" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --header "If-Match: 68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501" \
  --data '{"branchName":"Otherwise","branchType":"FALLBACK","conditions":[]}'
```

```json
{
  "data": {
    "node": {
      "_id": "65f1a2b3c4d5e6f708192a3e",
      "name": "XP over 100?",
      "type": "router",
      "order": 2,
      "continueOnFailure": false,
      "retryOnFailure": false,
      "options": {
        "executePaths": "first_matching",
        "branches": [
          { "branchName": "XP > 100", "branchType": "CONDITION", "workflowNodeIds": ["65f1a2b3c4d5e6f708192a3d"] },
          { "branchName": "Otherwise", "branchType": "FALLBACK", "workflowNodeIds": [] }
        ]
      }
    },
    "revision": "68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501",
    "redactedPaths": []
  }
}
```

## Response

HTTP `201` with the router step in `{ node, revision, redactedPaths }`, and the revision in the `ETag` header. The response is the readback: `options.branches` shows the new branch at the index you will use to add steps to it.

The branch index is its position in `options.branches`, counted from `0`.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `data` | `object` | - | One step with the workflow revision it left behind. |
| `data.node` | `object` | - | A step as returned by the API. |
| `data.node._id` | `string` | - | Step identifier. |
| `data.node.name` | `string` | - | Step name. |
| `data.node.type` | `string` | - | Step type. |
| `data.node.order` | `integer` | - | Position on the main line, from 1. |
| `data.node.continueOnFailure` | `boolean` | - | Whether a failure stops the run. |
| `data.node.retryOnFailure` | `boolean` | - | Whether the step is retried on failure. |
| `data.node.options` | `object` | - | Stored step options, with every credential replaced by `["__REDACTED__"]`. (Nullable) |
| `data.node.status` | `string` | - | Last run state of the step, when one exists. |
| `data.node.nestedNodes` | `object[]` | - | Present on a router: each branch carries its own `workflowNodeIds` and `nestedNodes`. |
| `data.revision` | `string` | - | Revision after the write. |
| `data.redactedPaths` | `string[]` | - | Credential locations replaced in this response. |

### Example response (201)

```json
{
  "data": {
    "node": {
      "_id": "65f1a2b3c4d5e6f708192a3c",
      "name": "XP over 100?",
      "type": "router",
      "order": 1,
      "continueOnFailure": false,
      "retryOnFailure": false,
      "options": {
        "executePaths": "first_matching",
        "branches": [
          {
            "branchName": "XP > 100",
            "branchType": "CONDITION",
            "workflowNodeIds": []
          }
        ]
      }
    },
    "revision": "68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501",
    "redactedPaths": []
  }
}
```

## Errors

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 400 | `VALIDATION_FAILED` | The step is not a router, or a required field is missing or invalid. Send `branchName` and `branchType`, and `conditions` for a condition branch. |
| 401 | `AUTHENTICATION_REQUIRED` | No key, or an unknown key. Send a valid key. |
| 401 | `API_KEY_REQUIRED` | A valid admin session was used, because this API accepts community API keys only. Use a community API key. |
| 403 | `API_KEY_PERMISSION_DENIED` | No `workflowBuilder`. Grant the permission. |
| 413 | `VALIDATION_FAILED` | The request body is too large to read. Nothing was written. Send a smaller body; a large import is better split into a create plus step writes. |
| 415 | `UNSUPPORTED_MEDIA_TYPE` | The body was not declared `application/json`. Send the header. |

### Fix the data

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `NODE_NOT_FOUND` | The step is not part of this workflow. Read the workflow for its step ids. |
| 404 | `BRANCH_NOT_FOUND` | No branch at that index on this router. |
| 409 | `VERSION_CONFLICT` | You sent an `If-Match` that is not the current revision. `meta.currentRevision` carries the live value. Re-read and resend. |
| 409 | `WORKFLOW_LOCKED` | Another write to this workflow held the lock for too long, and this write was not applied. Re-read and retry. |

### Retry with backoff

| Status | Code | What to do |
| --- | --- | --- |
| 429 | `RATE_LIMITED` | The community's API rate limit was reached. Wait for the window to reset, then retry. |
| 500 | `INTERNAL_ERROR` | Unexpected failure. Re-read the router before retrying. |
| 503 | `LOCK_UNAVAILABLE` | The write lock store did not answer, so nothing was written. Retry shortly. |

**Retries:** Not idempotent: two accepted calls create two branches with the same name. A retry after a timeout can leave a duplicate branch, so read the router before resending.

## Next step

- [Update a router branch](https://docs.returning.ai/api-reference/workflow-builder/update-a-router-branch.md): `PATCH /v1/workflow-builder/data-workflows/{workflowId}/nodes/{nodeId}/branches/{branchIndex}`.
