Returning.AIDocs
v1

API reference / Workflow Builder

.md

Test one step

Runs one step.

Last updated 28 Oct 2026API v1

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.

FailureMeaningRecovery
403 API_KEY_PERMISSION_DENIEDThe key has no workflowBuilder permissionGrant the permission.
404 NODE_NOT_FOUNDThe step is not part of this workflowRead 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#

workflowId#stringREQUIRED
Workflow identifier: 24 hexadecimal characters, as returned in _id.

Rule^[0-9a-fA-F]{24}$

Eg65f1a2b3c4d5e6f708192a3b

nodeId#stringREQUIRED
Step identifier: 24 hexadecimal characters, as returned in the step's _id.

Rule^[0-9a-fA-F]{24}$

Eg65f1a2b3c4d5e6f708192a3c

Headers#

Authorization#stringREQUIRED
Community API key: Bearer <API_KEY>.

EgBearer <API_KEY>

Content-Type#stringREQUIRED
Request body format.

Ruleapplication/json

Body#

mode#stringOPTIONAL
live (default) runs the real step services and may write or pay rewards. dry-run is opt-in and side-effect free.

Ruledry-run, live; default live

Eg"dry-run"

input#objectOPTIONAL
Earlier step results keyed by step id, for a single-step test.

Eg{"65f1a2b3c4d5e6f708192a3c": {"XP": 150}}

triggerPayload#stringOPTIONAL

Watch for

  • The default is 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.
  • A downstream step's references resolve only from 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.
  • 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 with input is 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.sourceCode and 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:

BASH
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:

BASH
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:

BASH
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.

JSON
{
  "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.

  • 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.

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.

data#object
One step test result.

Eg{ ... }

mode#string
dry-run or live.

Eg"dry-run"

sideEffects#string
none for a dry run, real for a live run.

Eg"none"

workflowId#string
Workflow under test.

Eg"65f1a2b3c4d5e6f708192a3b"

nodeId#string
Step under test.

Eg"65f1a2b3c4d5e6f708192a3d"

status#string
executed, simulated, skipped or failed.

Eg"simulated"

simulated#boolean
True when nothing was written, sent or called.

Egtrue

wouldDo#object
Resolved inputs the step would have used.

Eg{ ... }

warnings#string[]
Anything the caller should know about the result.

Eg[]

trace#object[]
One entry per step the test walked.

Eg[ ... ]

nodeId#string
Step identifier.

Eg"65f1a2b3c4d5e6f708192a3d"

name#string
Step name.

Eg"Add 10 XP"

type#string
Step type.

Eg"platform-action"

status#string
executed for a read-only step that really ran, simulated for one that would have written, skipped or failed.

Eg"simulated"

warnings#string[]
Anything the caller should know about the entry.

Eg[]

branch#string
Branch name, for a step inside a router branch.

Eg"XP > 100"

branchIndex#integer
Branch index in the trace. Results are 1-based, matching the router result the canvas shows; position.branchIndex and the branch path parameter count from 0.

Eg1

output#object
Step result, for an executed step.

RuleNullable

Eg{"xp": 150}

wouldDo#object
Resolved inputs, for a simulated step.

Eg{"action": "post-user-fields"}

code#string
Machine code for a failure.

Eg"USER_NOT_FOUND"

message#string
Human explanation for a failure.

Eg"User not found"

redactedPaths#string[]
Credential locations replaced in this response.

Eg[]

output#object
Step result, for an executed step.

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

400VALIDATION_FAILEDFix the request
mode is neither dry-run nor live, or the body is not a JSON object. Send a supported mode.
401AUTHENTICATION_REQUIREDFix the request
No key, or an unknown key. Send a valid key.
401API_KEY_REQUIREDFix the request
A valid admin session was used, because this API accepts community API keys only. Use a community API key.
403API_KEY_PERMISSION_DENIEDFix the request
The key has no workflowBuilder permission. Grant the permission.
413VALIDATION_FAILEDFix the request
The request body is too large to read. Nothing was written. Send a smaller body; a large import is better split into a create plus step writes.
415UNSUPPORTED_MEDIA_TYPEFix the request
The body was not declared application/json. Send the header.

Fix the data01

404NODE_NOT_FOUNDFix the data
The step is not part of this workflow. Read the workflow for its step ids.

Retry with backoff02

429RATE_LIMITEDRetry with backoff
The community's API rate limit was reached. Wait for the window to reset, then retry.
500INTERNAL_ERRORRetry with backoff
Unexpected failure. Retry with bounded backoff in dry-run mode only.

Retries

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."
}

Next step#

Test a whole workflowPOST/v1/workflow-builder/data-workflows/{workflowId}/test