POST /{workflowId}/nodes/{nodeId}/test needs workflowBuilder.mode decides whether the real step services are called:mode: "live" is the default, and matches the canvas Test button. The step really runs: it may write member fields, send mail, call an outside system or pay a reward.mode: "dry-run" is opt-in and side-effect free. Read-only steps really run; everything that would write, send or call is resolved and reported instead of executed.sideEffects states which one you got: "real" or "none".Authorization: Bearer <API_KEY> with workflowBuilder, on a workflow of the key's community. One permission covers both modes; there is no separate live-test permission and no confirmation header. What protects you is mode, so send "dry-run" explicitly while you are still editing.If-Match is needed: neither mode changes the workflow configuration.| Failure | Meaning | Recovery |
|---|---|---|
403 API_KEY_PERMISSION_DENIED | The key has no workflowBuilder permission | Grant the permission. |
404 NODE_NOT_FOUND | The step is not part of this workflow | Read the workflow for its step ids. |
mode. It may write and pay:wouldDo carries the resolved inputs.{
"data": {
"mode": "dry-run",
"sideEffects": "none",
"workflowId": "65f1a2b3c4d5e6f708192a3b",
"nodeId": "65f1a2b3c4d5e6f708192a3d",
"status": "simulated",
"simulated": true,
"wouldDo": {
"action": "post-user-fields",
"selectUser": { "type": "email", "value": "trader@example.com" },
"users": [{ "id": 98765, "_id": "65f1a2b3c4d5e6f708192a60" }],
"fields": [{ "label": "XP", "field": "xp", "logic": "add", "value": "10" }]
},
"warnings": [],
"trace": [{ "nodeId": "65f1a2b3c4d5e6f708192a3d", "name": "Add 10 XP", "type": "platform-action", "status": "simulated", "warnings": [] }],
"redactedPaths": []
}
}200 with a single result object.sideEffects is "none" for a dry run and "real" for a live run. Check it before reading anything else.status is executed for a read-only step that really ran, simulated for a step that would have written, skipped or failed.wouldDo is the resolved input for a simulated step: who would be written, which field, which amount and which logic; the request that would be sent for an HTTP step, with credentials redacted; the row count and a sample for a bulk step; the recipient count and template for an email step.output is present only for an executed step.trace has one entry for a normal step. A router test walks the branches its conditions select, so it returns one entry per step on the path it would take.GET /runs/{runId} on the workflow's latest run before and after must be the same run, and the member's field value must be unchanged.| Status and code | When | Recovery |
|---|---|---|
400 VALIDATION_FAILED | mode is neither dry-run nor live, or the body is not a JSON object | Send a supported mode. |
401 AUTHENTICATION_REQUIRED | No key, or an unknown key | Send a valid key. |
403 API_KEY_PERMISSION_DENIED | The key has no workflowBuilder permission | Grant the permission. |
404 NODE_NOT_FOUND | The step is not part of this workflow | Read the workflow for its step ids. |
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 | Retry with bounded backoff in dry-run mode only. |
live. An empty body therefore runs the step for real. Send {"mode":"dry-run"} while you are still editing.wouldDo is built from redacted options, so a credential appears as ["__REDACTED__"] and its path is listed in redactedPaths. A dry run never reveals a stored credential.input (keyed by step id) and triggerPayload. Without a seed, a value resolves to nothing and the step may report a failure that would not happen in a run.UNRESOLVED_SIMULATED_REFERENCE; testing that step directly with input is how you cover it.wouldDo.sourceCode and a warning that the output is unavailable, because a simulated step produces no output. A live test runs the source for real.POST /{workflowId}/test with mode: "dry-run" to walk the whole workflow and see which branch it takes.PATCH /{workflowId}/nodes/{nodeId} to fix what the dry run showed.POST /{workflowId}/deploy once the dry run is clean.curl --location 'https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes/65f1a2b3c4d5e6f708192a3c/test' \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Content-Type: application/json' \
--data '{
"mode": "dry-run"
}'{
"data": {
"mode": "dry-run",
"sideEffects": "none",
"workflowId": "65f1a2b3c4d5e6f708192a3b",
"nodeId": "65f1a2b3c4d5e6f708192a3d",
"status": "simulated",
"simulated": true,
"wouldDo": {
"action": "post-user-fields",
"selectUser": {
"type": "email",
"value": "trader@example.com"
},
"users": [
{
"id": 98765,
"_id": "65f1a2b3c4d5e6f708192a60"
}
],
"fields": [
{
"label": "XP",
"field": "xp",
"logic": "add",
"value": "10"
}
]
},
"warnings": [],
"trace": [
{
"nodeId": "65f1a2b3c4d5e6f708192a3d",
"name": "Add 10 XP",
"type": "platform-action",
"status": "simulated",
"warnings": []
}
],
"redactedPaths": []
}
}