Returning.AIDocs
v1

Guides / Workflow Builder

.md

Test without side effects, then confirm a live run

A workflow that pays members needs two separate answers. First: it does what you think, without paying anyone. Second: it really runs when the trigger fires, and it pays exactly once.

The API separates those two steps with one switch: mode. A dry run writes nothing; mode: "live" is the default, so the deliberate act is choosing the dry run, not the live one. Send mode: "dry-run" for every iteration and keep live for the run you mean.

The three modes, and what each one proves

ModeWhat it doesWhat it proves
mode: "dry-run"Read-only steps run for real; everything that writes, sends or calls is resolved and reportedThe logic, the branch taken, the resolved member and amounts
mode: "live" test (the default)Every step really runs, like a triggered runThe workflow executes end to end, but leaves no run in the history
Triggered runThe workflow runs because its trigger firedThe whole chain: trigger, workflow, run, side effects

1. Prepare a synthetic member

Take every field key from the catalog rather than guessing one: GET /catalog returns platformActionFields.get and platformActionFields.post, each entry carrying the key the step stores, the label the platform stores beside it, and whether the field is builtin or custom.

Use a member you created for this purpose, and read their current values through the platform's user endpoints. Record the baseline: field values, history rows, and any job counts you will compare.

2. Dry-run the workflow

POSTTest a whole workflowcURL
curl --request POST \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/test" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{"mode":"dry-run","triggerPayload":{"email":"trader@example.com","event_id":"sample-1","volume":"2.5"}}'

Readback, in this order:

  1. sideEffects: "none".
  2. Every write step is simulated, with wouldDo naming the member, the field, the amount and the logic.
  3. The branch the trace took is the branch you expected.
  4. Any step that is failed names why.

Then prove nothing changed: read the member's value and the workflow's runs again. The counts must be identical to the baseline.

3. Prove the negative case

Send a second dry run with a payload that must not pay: an ineligible member, a zero volume, a missing identifier.

POSTTest a whole workflowcURL
curl --request POST \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/test" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{"mode":"dry-run","triggerPayload":{"email":"trader@example.com","event_id":"sample-2","volume":"0"}}'

The trace must take the other branch, or fail the step for the reason you expect. A workflow that pays everybody is not a workflow you have tested.

4. Decide about the live test

A live test executes for real. It is useful when you need to know the steps run with real data, and it is not a substitute for a real trigger: it leaves no run in the history, so it cannot prove that the trigger is reachable or that the run was recorded.

It needs workflowBuilder like every other call, and nothing else: there is no live-test permission and no confirmation header. The mode is the only thing that makes it live, so send it deliberately:

POSTTest a whole workflowcURL
curl --request POST \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/test" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{"mode":"live","triggerPayload":{"email":"trader@example.com","event_id":"sample-live-1"}}'

Readback: sideEffects: "real", a status, the trace, and a historyId for the transient record the call created. Then read the member's value again: the delta must be exactly one reward.

Skip this mode when the workflow pays a live product and a dry run plus a triggered synthetic run already answers the question. The triggered run is the stronger evidence anyway.

5. Deploy and fire the real trigger

POSTDeploy a workflowcURL
curl --request POST \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/deploy" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "If-Match: 68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501"

Fire the trigger the way a real sender does, with a fresh event id and a synthetic member. Then read, in order:

  1. GET /{workflowId}/runs: a new run, with a status.
  2. GET /runs/{runId}: per-step results, including the branch taken.
  3. The member's value: exactly one delta.
  4. For a bulk step, the job's per-row detail.
  5. A replay of the same event: no second delta.

Each link is a separate claim. A run success with an unchanged member value is a failure that looks like a success.

6. Something that stays behind

A dry run writes no history, no step status, no member data and no outbound call. A live test leaves the member's new value: that part is real and must be recorded in your own evidence.

7. Failure handling

What you seeWhat it meansWhat to do
403 API_KEY_PERMISSION_DENIEDThe key has no workflowBuilder permissionGrant it
A live result where you meant a dry runmode was omitted, and live is the defaultSend mode: "dry-run", and check sideEffects in the response: none means nothing was written
The trace shows a step failed with UNRESOLVED_SIMULATED_REFERENCEThat step reads a step the dry run simulated, and a simulated step produces no output; the workflow status is failTest that step directly with input, or dry-run a workflow whose earlier steps are read-only
Run success, member unchangedThe step was skipped or the amount was zeroRead the run detail and the branch
Two deltas for one eventThe event reached the workflow twiceFix deduplication before the next cycle
Deploy succeeded with warnings you did not readValidation is advisory, so no validation finding refuses a deployRead warnings from the deploy response, and act on each one before the next cycle

8. Cleanup

Undeploy and delete the test workflow when it exists only for this check. Remove the synthetic member through the platform's user endpoints, or keep it deliberately and say so in your record. Delete any copy of a credential that the run produced in a log or artifact.

What the API does not cover

  • Cancelling a payment that a mistaken live run made. That is your reversal process.
  • Reading the member's own values. Those are platform endpoints.
  • A trigger that fires a test event from the platform's side. The sender fires it.
  • A permission that keeps a key from spending money. One permission covers the whole family, so the guard is the mode you pass and the instructions you give the agent or script holding the key.
  • Confirming that a member-facing screen shows the reward. That is a separate visual check.

Endpoints on this page