POST /{workflowId}/nodes 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 | The options do not match the step type, or position names a step that is not on the main line | Read the type's schema from GET /catalog, or read the workflow for current step ids. |
404 BRANCH_NOT_FOUND | position.branchIndex is out of range for that router | Read the router and use an existing branch index. |
AND groups: OR and WHERE groups are stored and deployed, and the runtime then treats the group as always true, so validation warns ROUTER_OPERATOR_UNSAFE:branches, and the step is also added to the workflow's step order automatically:{
"data": {
"node": {
"_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": "68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501",
"redactedPaths": []
}
}201 with { node, revision, redactedPaths }, and the new revision in the ETag header. node._id is the id every later step or reference uses.GET /{workflowId}: the step must appear at the intended place, with the branch membership you asked for, and the options must match what you sent after normalisation. For an HTTP step with a JSON or raw body, the stored body is a single template element containing the JSON text, which is the shape the canvas shows.| Status and code | When | Recovery |
|---|---|---|
400 VALIDATION_FAILED | Options do not match the type, a required field is missing, or position.after is not on the main line | Fix the named field. |
400 NODE_NOT_IN_WORKFLOW | A router's branch list names a step that is not part of this workflow | Reference only steps this workflow owns. |
400 LEADERBOARD_NOT_IN_COMMUNITY | A leaderboard step names another community's leaderboard | Use a leaderboard of the key's community. |
400 REDACTED_VALUE_WITHOUT_STORED_SECRET | A marker arrived on a new step, where nothing can be stored yet | Send the real credential. |
401 AUTHENTICATION_REQUIRED | No key, or an unknown key | Send a valid key. |
403 API_KEY_PERMISSION_DENIED | No workflowBuilder | Grant the permission. |
404 WORKFLOW_NOT_FOUND | Unknown workflow id, or another community's workflow | Use an id from this community. |
404 BRANCH_NOT_FOUND | position.branchIndex does not exist on that router | Read the router and use a valid 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 against the new revision. |
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. |
409 VERSION_CONFLICT, which is the safe outcome.position.after: "trigger" inserts before the current first step. Omitting position appends to the end of the main line.options must be the complete payload for the type. A partial options object is not merged with anything: there is nothing stored yet.platform-action field key must be a real field of the community. A read action accepts total_xp, total_coins, xp and coin; a write action accepts xp and coin; a custom field uses its own key. GET /catalog lists them. A key outside the list is stored as sent, and POST /{workflowId}/validate reports UNKNOWN_USER_FIELD as a warning, so read the list instead of guessing: a step that reads nothing fails at run time, not at write time.label you send is stored exactly as sent. Only a missing or empty label is filled from the catalog, for example Total XP for a read of total_xp and XP for a write to xp, so send the catalog's label unless you need a specific one.{{<readStepId>.Total XP}}, not the field key. Validation reports REFERENCE_UNKNOWN_FIELD when a reference names a label the step does not produce.auth must be present. Use {"type":"bearer-token","token":[]} for "no auth"; an empty value is not treated as a secret.redactedPaths in the response.type: "code" with options.inputs and options.sourceCode.code. It can be created, changed, duplicated, imported and tested like any other step; sideEffects: true because its source can write.continueOnFailure and retryOnFailure default to false. Set them explicitly when the workflow must continue past a failure.logic explicitly on every write step's field (add, subtract, overwrite). The stored default is overwrite, which can zero a balance, and nothing refuses the write: POST /{workflowId}/validate reports POST_LOGIC_REQUIRED as a warning.POST /{workflowId}/validate reports NODE_LISTED_TWICE as a warning.retryOnFailure is for a step that can fail and be repeated safely: one HTTP call, one email, one bulk update, or a write of one field. POST /{workflowId}/validate warns with RETRY_REPEATS_SIDE_EFFECTS on a router whose branches contain a step that pays or calls outside, and on a write step that sets more than one field, because a retry would repeat several side effects at once. Nothing refuses the write.PATCH /{workflowId}/nodes/{nodeId} to change the step you just added.POST /{workflowId}/nodes/{nodeId}/test with mode: "dry-run" to check this one step without side effects.POST /{workflowId}/validate once the workflow is complete.curl --location 'https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes' \
--header 'Authorization: Bearer <API_KEY>' \
--header 'If-Match: 162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c' \
--header 'Content-Type: application/json' \
--data '{
"type": "platform-action",
"name": "Read member XP",
"position": {
"after": "trigger"
},
"options": {
"action": "get-user-fields",
"selectUser": {
"type": "email",
"value": [
"{{65f1a2b3c4d5e6f708192a41.email}}"
]
},
"fields": [
{
"label": "Total XP",
"field": "total_xp"
}
]
}
}'{
"data": {
"node": {
"_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": "68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501",
"redactedPaths": []
}
}