Add a step
Adds one step to a workflow: at a position on the main line, or inside a router branch.
- Method
- POST
- Path
https://api.returning.ai / v1/ workflow-builder/ data-workflows/ {workflowId}/ nodes - Permission
- workflowBuilder
- Retries
- Adding a step is not idempotent: two accepted calls create two steps.
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 | 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. |
Behaviour#
The step's options are validated against its type before anything is stored, and the position is resolved by the server.
A branch step is registered with its branch in the same call. The response carries the stored step and the workflow's new revision.
Request#
Path parameters#
_id.Rule^[0-9a-fA-F]{24}$
Eg65f1a2b3c4d5e6f708192a3b
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#
Rulehttp, platform-action, math, send-email, send-emails, code, router, users-bulk-update
Eg"platform-action"
RuleMax 200 chars
Eg"Read member XP"
Eg{"after": "trigger"}
trigger for the first position.Eg"trigger"
Eg"65f1a2b3c4d5e6f708192a3e"
RuleMin 0
Eg0
GET /catalog.Eg{ ... }
false.Egfalse
false.Egfalse
node-<index>.RuleMax 120 chars
Eg"read-member"
Watch for
position.after: "trigger"inserts before the current first step. Omittingpositionappends to the end of the main line.- A branch step is created on the main line first and then registered with its branch, so a step that is in a branch still appears in the workflow's id list.
optionsmust be the complete payload for the type. A partial options object is not merged with anything: there is nothing stored yet.- A
platform-actionfield key must be a real field of the community. A read action acceptstotal_xp,total_coins,xpandcoin; a write action acceptsxpandcoin; a custom field uses its own key.GET /cataloglists them. A key outside the list is stored as sent, andPOST /{workflowId}/validatereportsUNKNOWN_USER_FIELDas a warning, so read the list instead of guessing: a step that reads nothing fails at run time, not at write time. - A
labelyou send is stored exactly as sent. Only a missing or empty label is filled from the catalog, for exampleTotal XPfor a read oftotal_xpandXPfor a write toxp, so send the catalog's label unless you need a specific one. - A read step's result is keyed by that label, so a later step references
{{<readStepId>.Total XP}}, not the field key. Validation reportsREFERENCE_UNKNOWN_FIELDwhen a reference names a label the step does not produce. - An HTTP step's
authmust be present. Use{"type":"bearer-token","token":[]}for "no auth"; an empty value is not treated as a secret. - A credential-named header or parameter is redacted on read even when you authored it as a template. See
redactedPathsin the response. - A Code step is a normal step type here:
type: "code"withoptions.inputsandoptions.sourceCode.code. It can be created, changed, duplicated, imported and tested like any other step;sideEffects: truebecause its source can write. - A leaderboard step reads that leaderboard's own community, so it may only name a leaderboard of the key's community.
continueOnFailureandretryOnFailuredefault tofalse. Set them explicitly when the workflow must continue past a failure.- Send
logicexplicitly on every write step's field (add,subtract,overwrite). The stored default isoverwrite, which can zero a balance, and nothing refuses the write:POST /{workflowId}/validatereportsPOST_LOGIC_REQUIREDas a warning. - A step id should appear once across a router's branch lists: a second listing makes a run execute the step twice. Nothing refuses the write;
POST /{workflowId}/validatereportsNODE_LISTED_TWICEas a warning. retryOnFailureis 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}/validatewarns withRETRY_REPEATS_SIDE_EFFECTSon 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.
More examples and details
Append an HTTP step after an existing step:
curl --request POST \
--url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes" \
--header "Authorization: Bearer <API_KEY>" \
--header "Content-Type: application/json" \
--header "If-Match: 162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c" \
--data '{"type":"http","name":"Notify receiver","position":{"after":"65f1a2b3c4d5e6f708192a3c"},"options":{"method":"POST","url":["https://receiver.example.com/trade-bonus"],"params":{},"headers":{"X-Api-Key":["<RECEIVER_KEY>"]},"bodyType":"json","body":{"member":["{{65f1a2b3c4d5e6f708192a41.email}}"]},"auth":{"type":"bearer-token","token":[]}}}'
A router with one condition branch and one fallback. Conditions use 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:
curl --request POST \
--url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes" \
--header "Authorization: Bearer <API_KEY>" \
--header "Content-Type: application/json" \
--header "If-Match: 68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501" \
--data '{"type":"router","name":"XP over 100?","position":{"after":"65f1a2b3c4d5e6f708192a3c"},"options":{"executePaths":"first_matching","branches":[{"branchName":"XP > 100","branchType":"CONDITION","conditions":[{"id":"group-1","operator":"AND","nestedGroups":[{"id":"nested-1","operator":"AND","conditions":[{"id":"condition-1","key":["{{65f1a2b3c4d5e6f708192a3c.Total XP}}"],"type":"number","operator":"greater_than","value":["100"]}]}]}]},{"branchName":"Otherwise","branchType":"FALLBACK","conditions":[]}]}}'
A step inside the router's first branch. The branch index is the position in branches, and the step is also added to the workflow's step order automatically:
curl --request POST \
--url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes" \
--header "Authorization: Bearer <API_KEY>" \
--header "Content-Type: application/json" \
--header "If-Match: 9ab88dc5d9fdaada545d24e2d76d58cd758c7b99eb905df867d90cdda294e12f" \
--data '{"type":"platform-action","name":"Add 10 XP","position":{"routerNodeId":"65f1a2b3c4d5e6f708192a3e","branchIndex":0},"options":{"action":"post-user-fields","selectUser":{"type":"email","value":["{{65f1a2b3c4d5e6f708192a41.email}}"]},"fields":[{"label":"XP","field":"xp","logic":"add","value":["10"]}]}}'
{
"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": []
}
}
curl --request POST \
--url 'https://api.returning.ai/v1/workflow-builder/data-workflows/<workflowId>/nodes' \
--header 'Authorization: Bearer <API_KEY>' \
--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"
}
]
}
}'
Response#
HTTP 201 with { node, revision, redactedPaths }, and the new revision in the ETag header. node._id is the id every later step or reference uses.
Read back with 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.
Eg{ ... }
Eg{ ... }
Eg"65f1a2b3c4d5e6f708192a3c"
Eg"Read member XP"
Eg"platform-action"
Eg1
Egfalse
Egfalse
["__REDACTED__"].RuleNullable
Eg{ ... }
Eg"success"
workflowNodeIds and nestedNodes.Eg"68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501"
Eg[]
{
"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": []
}
}
Errors#
Fix the request09
VALIDATION_FAILEDFix the requestposition.after is not on the main line. Fix the named field.NODE_NOT_IN_WORKFLOWFix the requestLEADERBOARD_NOT_IN_COMMUNITYFix the requestREDACTED_VALUE_WITHOUT_STORED_SECRETFix the requestAUTHENTICATION_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 data04
WORKFLOW_NOT_FOUNDFix the dataBRANCH_NOT_FOUNDFix the dataposition.branchIndex does not exist on that router. Read the router and use a valid index.VERSION_CONFLICTFix the dataIf-Match that is not the current revision. meta.currentRevision carries the live value. Re-read and resend against the new revision.WORKFLOW_LOCKEDFix the dataRetry with backoff03
RATE_LIMITEDRetry with backoffINTERNAL_ERRORRetry with backoffLOCK_UNAVAILABLERetry with backoffRetries
Adding a step is not idempotent: two accepted calls create two steps. A retry after a timeout can leave a duplicate, so read the workflow and check for the step before resending. A retry with the revision from before the first attempt is refused with 409 VERSION_CONFLICT, which is the safe outcome.
{
"meta": {
"status": "error",
"statusCode": 400,
"code": "VALIDATION_FAILED",
"errors": [
{
"path": "node.options.url",
"message": "URL is required"
}
],
"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."
}