POST /{workflowId}/nodes/{nodeId}/move needs 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. |
{
"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": []
}
}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.POST /{workflowId}/validate: a step that now runs before the step it reads reports REFERENCE_NOT_UPSTREAM.| Status and code | When | Recovery |
|---|---|---|
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. |
403 API_KEY_PERMISSION_DENIED | No workflowBuilder | Grant the permission. |
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. |
415 UNSUPPORTED_MEDIA_TYPE | The body was not declared application/json | Send the header. |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. |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.after: "trigger" means the first position on the main line. Any other value must be a main-line step id.position: {routerNodeId, branchIndex}.order values is stale afterwards.data.node finds nothing here.POST /{workflowId}/validate to catch references the new order broke.POST /{workflowId}/nodes/{nodeId}/test for a dry run in the new position.GET /{workflowId} for a clean read of the order.curl --location 'https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes/65f1a2b3c4d5e6f708192a3c/move' \
--header 'Authorization: Bearer <API_KEY>' \
--header 'If-Match: 162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c' \
--header 'Content-Type: application/json' \
--data '{
"after": "trigger"
}'{
"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": []
}
}