# Duplicate a router branch

Copies one branch, including the steps that belong to it, and appends the copy to the router.

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

## When to use this

Use it to build a variant quickly: duplicate a branch, change its conditions, and add it as another case.

## 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 |
| --- | --- | --- |
| `404 BRANCH_NOT_FOUND` | No branch at that index | Read the router and use an existing index. |

## Behaviour

The copy's steps are new steps with new ids, and references between them are rewired to the copies, including a `{{<stepId>.path}}` template.

## 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}$`) |
| `branchIndex` | `integer` | Yes | Zero-based index of the branch inside the router step, in stored order. (Min 0) |

### 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. |

### Watch for

- The copy has the same conditions as the original. Change them, or the second branch can never be reached when `executePaths` is `first_matching`.
- The copied branch is appended last, so a duplicated fallback ends up after the original fallback and never runs.
- Credentials inside copied steps are copied as stored and are redacted in the response.
- A Code step inside the copied branch is copied like any other step, with its source.

### Example request

```bash
curl --request POST \
  --url 'https://api.returning.ai/v1/workflow-builder/data-workflows/<workflowId>/nodes/<nodeId>/branches/<branchIndex>/duplicate' \
  --header 'Authorization: Bearer <API_KEY>'
```

### More examples

```bash
curl --request POST \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes/65f1a2b3c4d5e6f708192a3e/branches/0/duplicate" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "If-Match: 162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c"
```

```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": "XP > 100 (Copy)", "branchType": "CONDITION", "workflowNodeIds": ["65f1a2b3c4d5e6f708192a4a"] }
        ]
      }
    },
    "revision": "68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501",
    "redactedPaths": []
  }
}
```

## Response

HTTP `201` with the router step and the workflow's new revision. The response is the readback: the copied branch is the last entry in `options.branches`, with a `(Copy)` suffix and its own member ids.

Read `GET /{workflowId}` to see the copied steps nested under the new branch. They carry new ids, so anything that referenced the originals still points at the originals.

### 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": []
          },
          {
            "branchName": "XP > 100 (Copy)",
            "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 an id is malformed. Use a router step and valid ids. |
| 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. |

### 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. Read the router and use an existing index. |
| 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. A retry after a timeout can leave a duplicate, so read the router before resending.

## Next step

- [Validate a stored workflow](https://docs.returning.ai/api-reference/workflow-builder/validate-a-stored-workflow.md): `POST /v1/workflow-builder/data-workflows/{workflowId}/validate`.
