# Reorder a main-line step

Moves one main-line step to a new place on the main line.

- Endpoint: `POST https://api.returning.ai/v1/workflow-builder/data-workflows/{workflowId}/nodes/{nodeId}/move`
- Section: Workflow Builder / Steps
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `workflowBuilder`
- Retries: Send `If-Match` if a duplicate move matters: a retry with the same revision is refused with `409 VERSION_CONFLICT` after the first attempt lands.
- Guide: generated from the published specification
- Last updated: 28 Oct 2026
- Web page: https://docs.returning.ai/api-reference/workflow-builder/reorder-a-main-line-step

## 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` | `nodeId` is not on the main line, or `after` names a step that is not on the main line | Read the workflow and pick a main-line step. |

## Behaviour

The whole main line is renumbered from 1 so the order is always a clean sequence, and the workflow's authored order is rewritten to match.

It moves within the main line only. A step inside a router branch cannot be moved with this route, and a main-line step cannot be moved into a branch.

## 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 |
| --- | --- | --- | --- |
| `after` | `string` | Yes | Step id to move after, or `trigger` for the first position. |

### Watch for

- `after: "trigger"` means the first position on the main line. Any other value must be a main-line step id.
- Branch members are not renumbered by this route, and their relative order inside the branch is preserved.
- Moving across branches is a delete plus a create, because no move exists for that case. Recreate the step inside the target branch with `position: {routerNodeId, branchIndex}`.
- A move renumbers every main-line step, so any external note that stored `order` values is stale afterwards.
- The route returns the workflow, not the step, so a client that only reads `data.node` finds nothing here.

### Example request

```bash
curl --request POST \
  --url 'https://api.returning.ai/v1/workflow-builder/data-workflows/<workflowId>/nodes/<nodeId>/move' \
  --header 'Authorization: Bearer <API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "after": "trigger"
  }'
```

### More examples

Move a step to the front:

```bash
curl --request POST \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes/65f1a2b3c4d5e6f708192a3d/move" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --header "If-Match: 162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c" \
  --data '{"after":"trigger"}'
```

Move it after another step:

```bash
curl --request POST \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes/65f1a2b3c4d5e6f708192a3d/move" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --header "If-Match: 68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501" \
  --data '{"after":"65f1a2b3c4d5e6f708192a3c"}'
```

```json
{
  "data": {
    "_id": "65f1a2b3c4d5e6f708192a3b",
    "communityId": "65f1a2b3c4d5e6f708192a50",
    "name": "VIP bonus",
    "status": "draft",
    "outgoingWebhookUrl": null,
    "createdAt": "2026-10-24T09:15:00.000Z",
    "updatedAt": "2026-10-24T12:40:11.000Z",
    "trigger": null,
    "nodes": [
      { "_id": "65f1a2b3c4d5e6f708192a3d", "name": "Add 10 XP", "type": "platform-action", "order": 1, "continueOnFailure": false, "retryOnFailure": false, "options": { "action": "post-user-fields" } },
      { "_id": "65f1a2b3c4d5e6f708192a3c", "name": "Read member XP", "type": "platform-action", "order": 2, "continueOnFailure": false, "retryOnFailure": false, "options": { "action": "get-user-fields" } }
    ],
    "revision": "68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501",
    "redactedPaths": []
  }
}
```

## Response

HTTP `200` with the whole workflow and its new revision. The response is the readback: `nodes` is the new main-line order with `order` renumbered from 1, and branch members keep their relative order inside their branch.

Order decides what a step can read. After a move, run `POST /{workflowId}/validate`: a step that now runs before the step it reads reports `REFERENCE_NOT_UPSTREAM`.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `data` | `object` | - | A workflow with its trigger, its steps in canvas order and its revision. |
| `data._id` | `string` | - | Workflow identifier. |
| `data.communityId` | `string` | - | Community the workflow belongs to. |
| `data.name` | `string` | - | Workflow name. |
| `data.status` | `string` | - | `draft`, `deployed` or `undeployed`. |
| `data.outgoingWebhookUrl` | `string` | - | Failure webhook URL, when one is set. (Nullable) |
| `data.createdAt` | `string` | - | Creation time. |
| `data.updatedAt` | `string` | - | Last authored change. |
| `data.trigger` | `object` | - | The trigger, or `null` when the workflow has none. (Nullable) |
| `data.trigger._id` | `string` | - | Trigger identifier. Use it as the source of a reference. |
| `data.trigger.name` | `string` | - | Display name. |
| `data.trigger.type` | `string` | - | Trigger type. |
| `data.trigger.status` | `string` | - | Trigger lifecycle state, for example `idle`. |
| `data.trigger.options` | `object` | - | Trigger options. A webhook key is shown only in the response that created it. (Nullable) |
| `data.nodes` | `object[]` | - | Main-line steps in canvas order, routers carrying their branches. |
| `data.nodes._id` | `string` | - | Step identifier. |
| `data.nodes.name` | `string` | - | Step name. |
| `data.nodes.type` | `string` | - | Step type. |
| `data.nodes.order` | `integer` | - | Position on the main line, from 1. |
| `data.nodes.continueOnFailure` | `boolean` | - | Whether a failure stops the run. |
| `data.nodes.retryOnFailure` | `boolean` | - | Whether the step is retried on failure. |
| `data.nodes.options` | `object` | - | Stored step options, with every credential replaced by `["__REDACTED__"]`. (Nullable) |
| `data.nodes.status` | `string` | - | Last run state of the step, when one exists. |
| `data.nodes.nestedNodes` | `object[]` | - | Present on a router: each branch carries its own `workflowNodeIds` and `nestedNodes`. |
| `data.revision` | `string` | - | 64 hexadecimal characters. Send it back as `If-Match`; it also arrives as the `ETag` header. |
| `data.redactedPaths` | `string[]` | - | Every location where a credential was replaced by `["__REDACTED__"]`. |

### Example response (200)

```json
{
  "data": {
    "_id": "65f1a2b3c4d5e6f708192a3b",
    "communityId": "65f1a2b3c4d5e6f708192a50",
    "name": "VIP bonus",
    "status": "draft",
    "outgoingWebhookUrl": null,
    "createdAt": "2026-10-24T09:15:00.000Z",
    "updatedAt": "2026-10-24T09:20:11.000Z",
    "trigger": {
      "_id": "65f1a2b3c4d5e6f708192a41",
      "name": "Trade bonus trigger",
      "type": "webhook",
      "status": "idle",
      "options": {
        "tokenExchangeEnabled": false,
        "apiKey": {
          "enabled": false
        },
        "aesKey": {
          "enabled": false
        }
      }
    },
    "nodes": [
      {
        "_id": "65f1a2b3c4d5e6f708192a3c",
        "name": "Read member XP",
        "type": "platform-action",
        "order": 1,
        "continueOnFailure": false,
        "retryOnFailure": false,
        "options": {
          "action": "get-user-fields",
          "selectUser": {
            "type": "email",
            "value": ["{{65f1a2b3c4d5e6f708192a41.email}}"]
          },
          "fields": [
            {
              "label": "Total XP",
              "field": "total_xp"
            }
          ]
        }
      }
    ],
    "revision": "162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c",
    "redactedPaths": []
  }
}
```

## Errors

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 400 | `VALIDATION_FAILED` | The step or the `after` target is not on the main line, or the body is missing `after`. Use main-line step ids only. |
| 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. |
| 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 workflow before retrying. |
| 503 | `LOCK_UNAVAILABLE` | The write lock store did not answer, so nothing was written. Retry shortly. |

**Retries:** Send `If-Match` if a duplicate move matters: a retry with the same revision is refused with `409 VERSION_CONFLICT` after the first attempt lands. Without the header a retry applies again, and the second move is a no-op because the step is already in place. Moving a step to the position it already has is a successful no-op that still changes nothing except `updatedAt`; the revision does not change in that case.

## Next step

- [Duplicate a step](https://docs.returning.ai/api-reference/workflow-builder/duplicate-a-step.md): `POST /v1/workflow-builder/data-workflows/{workflowId}/nodes/{nodeId}/duplicate`.
