Test one step
Runs one step.
- Method
- POST
- Path
https://api.returning.ai / v1/ workflow-builder/ data-workflows/ {workflowId}/ nodes/ {nodeId}/ test - Permission
- workflowBuilder
- Retries
- A dry run is safe to retry: it writes nothing and calls nothing.
Authentication#
- Header
Authorization: Bearer <API_KEY>- Permission
- workflowBuilder
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.
No 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. |
Behaviour#
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.
Both modes share one response shape, and sideEffects states which one you got: "real" or "none".
Request#
Path parameters#
Headers#
Body#
Watch for
- The default is
live. An empty body therefore runs the step for real. Send{"mode":"dry-run"}while you are still editing. wouldDois built from redacted options, so a credential appears as["__REDACTED__"]and its path is listed inredactedPaths. A dry run never reveals a stored credential.- A downstream step's references resolve only from
input(keyed by step id) andtriggerPayload. Without a seed, a value resolves to nothing and the step may report a failure that would not happen in a run. - A simulated step produces no output. In a workflow test a later step that reads it fails with
UNRESOLVED_SIMULATED_REFERENCE; testing that step directly withinputis how you cover it. - A leaderboard step reads the leaderboard's own community, so a dry run fails it before any read if the leaderboard is not the key's community's.
- A Code step is tested like any other step. A dry run does not execute its source: it reports
wouldDo.sourceCodeand a warning that the output is unavailable, because a simulated step produces no output. A live test runs the source for real. - A live node test does not create a run in the workflow's history; it runs the step and returns the result. The whole-workflow live test is the one that behaves like a run.
More examples and details
A dry run of a write step, seeded with a trigger payload so the references resolve:
curl --request POST \
--url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes/65f1a2b3c4d5e6f708192a3d/test" \
--header "Authorization: Bearer <API_KEY>" \
--header "Content-Type: application/json" \
--data '{"mode":"dry-run","triggerPayload":{"email":"trader@example.com","trade_id":"T-1024","volume":"2.5"}}'
A dry run of a downstream step, seeded with the earlier step's result:
curl --request POST \
--url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes/65f1a2b3c4d5e6f708192a3d/test" \
--header "Authorization: Bearer <API_KEY>" \
--header "Content-Type: application/json" \
--data '{"mode":"dry-run","input":{"65f1a2b3c4d5e6f708192a3c":{"XP":150}}}'
A live run, which is what you get when you omit mode. It may write and pay:
curl --request POST \
--url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes/65f1a2b3c4d5e6f708192a3d/test" \
--header "Authorization: Bearer <API_KEY>" \
--header "Content-Type: application/json" \
--data '{"mode":"live","triggerPayload":{"email":"trader@example.com"}}'
A simulated write step: nothing was written, and 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": []
}
}
curl --request POST \
--url 'https://api.returning.ai/v1/workflow-builder/data-workflows/<workflowId>/nodes/<nodeId>/test' \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Content-Type: application/json' \
--data '{
"mode": "dry-run"
}'
Response#
HTTP 200 with a single result object.
sideEffectsis"none"for a dry run and"real"for a live run. Check it before reading anything else.statusisexecutedfor a read-only step that really ran,simulatedfor a step that would have written,skippedorfailed.wouldDois 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.outputis present only for an executed step.tracehas 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.
A dry run proves nothing was written. Confirm that with a read: 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.
Eg{ ... }
dry-run or live.Eg"dry-run"
none for a dry run, real for a live run.Eg"none"
Eg"65f1a2b3c4d5e6f708192a3b"
Eg"65f1a2b3c4d5e6f708192a3d"
executed, simulated, skipped or failed.Eg"simulated"
Egtrue
Eg{ ... }
Eg[]
Eg[ ... ]
Eg"65f1a2b3c4d5e6f708192a3d"
Eg"Add 10 XP"
Eg"platform-action"
executed for a read-only step that really ran, simulated for one that would have written, skipped or failed.Eg"simulated"
Eg[]
Eg"XP > 100"
position.branchIndex and the branch path parameter count from 0.Eg1
RuleNullable
Eg{"xp": 150}
Eg{"action": "post-user-fields"}
Eg"USER_NOT_FOUND"
Eg"User not found"
Eg[]
RuleNullable
Eg{"XP": 150}
{
"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": []
}
}
Errors#
Fix the request06
VALIDATION_FAILEDFix the requestmode is neither dry-run nor live, or the body is not a JSON object. Send a supported mode.AUTHENTICATION_REQUIREDFix the requestAPI_KEY_REQUIREDFix the requestAPI_KEY_PERMISSION_DENIEDFix the requestworkflowBuilder permission. Grant the permission.VALIDATION_FAILEDFix the requestUNSUPPORTED_MEDIA_TYPEFix the requestapplication/json. Send the header.Fix the data01
NODE_NOT_FOUNDFix the dataRetry with backoff02
RATE_LIMITEDRetry with backoffINTERNAL_ERRORRetry with backoffRetries
A dry run is safe to retry: it writes nothing and calls nothing. Repeat it as often as you like while you are still editing. A live run is not safe to retry. It can pay a reward or send an email, and a second accepted call can pay twice. Treat a timeout as an unknown outcome: read the member's field history or the run list before deciding whether to run it again. That is the reason the dry run exists: use it for every iteration, and keep the live call for the last one.
{
"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."
}