Reorder a main-line step
Moves one main-line step to a new place on the main line.
- Method
- POST
- Path
https://api.returning.ai / v1/ workflow-builder/ data-workflows/ {workflowId}/ nodes/ {nodeId}/ move - Permission
- workflowBuilder
- Retries
- Send
If-Matchif a duplicate move matters: a retry with the same revision is refused with409 VERSION_CONFLICTafter the first attempt lands.
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#
Headers#
Bearer <API_KEY>.EgBearer <API_KEY>
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.Eg162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c
Ruleapplication/json
Body#
trigger for the first position.Eg"trigger"
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
ordervalues is stale afterwards. - The route returns the workflow, not the step, so a client that only reads
data.nodefinds nothing here.
More examples and details
Move a step to the front:
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:
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"}'
{
"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": []
}
}
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"
}'
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.
Eg{ ... }
Eg"65f1a2b3c4d5e6f708192a3b"
Eg"65f1a2b3c4d5e6f708192a50"
Eg"VIP bonus"
draft, deployed or undeployed.Eg"draft"
RuleNullable
Egnull
Eg"2026-10-24T09:15:00.000Z"
Eg"2026-10-24T09:20:11.000Z"
null when the workflow has none.RuleNullable
Eg{ ... }
Eg"65f1a2b3c4d5e6f708192a41"
Eg"Trade bonus trigger"
Eg"webhook"
idle.Eg"idle"
RuleNullable
Eg{ ... }
Eg[ ... ]
Eg"65f1a2b3c4d5e6f708192a3c"
Eg"Read member XP"
Eg"platform-action"
Eg1
Egfalse
Egfalse
["__REDACTED__"].RuleNullable
Eg{ ... }
Eg"success"
workflowNodeIds and nestedNodes.If-Match; it also arrives as the ETag header.Eg"162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c"
["__REDACTED__"].Eg[]
{
"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 request06
VALIDATION_FAILEDFix the requestafter target is not on the main line, or the body is missing after. Use main-line step ids only.AUTHENTICATION_REQUIREDFix the requestAPI_KEY_REQUIREDFix the requestAPI_KEY_PERMISSION_DENIEDFix the requestworkflowBuilder. Grant the permission.VALIDATION_FAILEDFix the requestUNSUPPORTED_MEDIA_TYPEFix the requestapplication/json. Send the header.Fix the data03
NODE_NOT_FOUNDFix the dataVERSION_CONFLICTFix the dataIf-Match that is not the current revision. meta.currentRevision carries the live value. Re-read and resend.WORKFLOW_LOCKEDFix the dataRetry with backoff03
RATE_LIMITEDRetry with backoffINTERNAL_ERRORRetry with backoffLOCK_UNAVAILABLERetry with backoffRetries
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.
{
"meta": {
"status": "error",
"statusCode": 400,
"code": "VALIDATION_FAILED",
"errors": [
{
"path": "params.workflowId",
"message": "Id must be a 24 character hex string"
}
],
"requestId": "3f4d0e8b-7a6c-4f1e-9b2d-5a7c8e1f0a3b"
},
"message": "API request rejected error.",
"detail": "The request body is not valid.",
"solution": "Read error detail and try again."
}