Returning.AIDocs
v1

Guides / Workflow Builder

.md

Bind step inputs to trigger and earlier-step outputs

Most workflow failures that look like "the wrong value was written" are binding failures: a step reads a literal string where it needed a reference, or a reference that resolves to nothing. The API makes bindings visible, so a binding is something you can write, read back and dry-run before it runs.

The core rule

A value slot in step options is a list. A bare string in that list is text. A reference is either a template string or a reference object:

  • a template: "{{<nodeId>.<field>}}", where <nodeId> is a 24-character step id or the trigger's _id
  • an object: {"nodeId": "<stepId>", "field": "root.email", "defaultValue": "<safe-value>"}

A bare dotted string such as "webhook_trigger.email" is not a binding. It is text, and it is what a step writes when the binding is wrong. Do not save one.

The trigger's id is trigger._id, and a trigger payload field is normally read from root.

A reference into a read step uses the field's label, because that is how the step names its result. Take the label from platformActionFields in GET /catalog, for example Total XP, and reference {{<readStepId>.Total XP}}. A label you send is kept as sent, so the label in your node is the one later steps must use; a community that renamed its XP unit returns a different catalog label for the same key. Neither mistake is refused. A field key that is not one of the community's fields is stored as sent, and a reference to a label the step does not produce is saved too. POST /{workflowId}/validate reports them as the warnings UNKNOWN_USER_FIELD (with the allowed keys) and REFERENCE_UNKNOWN_FIELD, and deploy still proceeds. At run time the step reads or writes nothing: a write step with a mistyped key pays nothing and reports no error. Read the warnings before you deploy.

1. Read the field keys, then the two ids you need

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.

GETGet a workflowcURL
curl --request GET \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b" \
  --header "Authorization: Bearer <API_KEY>"

From the response take trigger._id (the source for trigger fields) and the _id of the step whose output you want to read. Never copy an id from another workflow: ids are per workflow even when the step names and payloads look identical.

2. Know how a reference path is read

The run path reads a reference with its own rules, and validation applies the same ones. Validation is advisory: every finding below is a warning in POST /{workflowId}/validate and in the deploy response, the write and the deploy both succeed, and the reference resolves to nothing at run time. Read the warnings before you deploy:

  • A reference object's field is read from the source step's result. The value of field is the step id itself, meaning "the whole result"; anything else is a path into it.
  • A {{nodeId.path}} template becomes path on the source step's result. A template whose first part is a 24-hex step id that is not in the workflow is reported as the warning REFERENCE_UNRESOLVABLE, and a run leaves it as literal text. A {{...}} whose first part is not an id at all, such as {{amount}}, is reported as the warning TEMPLATE_NOT_A_REFERENCE, because a run leaves that text as it is and the value never resolves. The id must also sit directly against the dot: {{<id> .path}} is not a reference. A copy is not the case to worry about: duplicate and import rewrite the templates they carry.
  • A path that names a label the step does not produce is REFERENCE_UNKNOWN_FIELD; a path that reads a field off a number or a list is REFERENCE_SHAPE_MISMATCH.
  • A step whose label is empty cannot be referenced: POST /{workflowId}/validate reports EMPTY_FIELD_LABEL when another step reads it, and the deploy still proceeds, so the reader resolves to nothing at run time.

3. Write the pair together

A reward step needs two independent bindings: the identity, and the value. Fixing only one is the most common half-fix.

POSTAdd a stepcURL
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":"platform-action","name":"Add volume XP","position":{"after":"65f1a2b3c4d5e6f708192a3c"},"options":{"action":"post-user-fields","selectUser":{"type":"email","value":[{"nodeId":"65f1a2b3c4d5e6f708192a41","field":"root.email","defaultValue":"trader@example.com"}]},"fields":[{"label":"XP","field":"xp","logic":"add","value":[{"nodeId":"65f1a2b3c4d5e6f708192a3c","field":"volume_xp","defaultValue":"0"}]}]}}'

The selector reads the trigger; the amount reads an earlier step. Both are references, so both resolve at run time.

The template form is shorter and equivalent:

PATCHUpdate a stepcURL
curl --request PATCH \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes/65f1a2b3c4d5e6f708192a3d" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --header "If-Match: 68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501" \
  --data '{"options":{"action":"post-user-fields","selectUser":{"type":"email","value":["{{65f1a2b3c4d5e6f708192a41.email}}"]},"fields":[{"label":"XP","field":"xp","logic":"add","value":["{{65f1a2b3c4d5e6f708192a3c.volume_xp}}"]}]}}'

4. Keep the default harmless

defaultValue is a test value, and the API's dry run does not use it. A dry run resolves a value exactly as a triggered run would, so a missing path stays missing. A live test uses the canvas test resolution, where a default stands in for a missing value, and a live test that pays really writes it. A triggered run never uses a default: it fails the step with Missing workflow mapping: source node ..., field ....

  • Keep defaults harmless anyway: a synthetic identifier, zero, or an empty CSV. A live test will use them.
  • Never use a real customer id or a reward amount as a default.
  • A test seeded with triggerPayload does not need a default at all, and a triggered run ignores it.

5. Prove the binding before it pays

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","volume_xp":"250"}}'

Read the trace entry for the step you changed:

  • wouldDo.selectUser.value must be the member identifier, not {{...}} text and not root.email.
  • wouldDo.fields[0].value must be the amount, not the field expression.
  • wouldDo.users must contain the member the platform resolved. An empty list means the selector did not resolve.

Then read the step back with GET /{workflowId}/nodes/{nodeId}: the stored options must show the reference, not the resolved sample. A stored sample that happens to look right is the failure this check exists to catch.

6. When a value does not resolve

What the dry run showsLikely causeFix
wouldDo.users empty, step failed with User not foundThe identity is text, or the field path is wrong for this triggerFix the selector's nodeId and field.
The written value is the mapping expressionThe value slot holds a bare stringReplace it with a template or a reference object.
REFERENCE_NOT_UPSTREAM from validateThe step reads a step that runs after it, or a sibling branchMove the step, or read an earlier step.
REFERENCE_MAY_BE_UNSET warningThe step reads a branch step that may not have runAccept it, or move the read into the same branch.
REFERENCE_UNRESOLVEDThe id belongs to another workflowUse this workflow's own step id.
REFERENCE_UNKNOWN_FIELDThe reference names a label the step does not produceUse the label on that step's fields[], or the catalog's label for the key.
REFERENCE_SHAPE_MISMATCHThe reference reads a field off a number or a listRead the value whole, or read a field of an object result.
REFERENCE_UNRESOLVABLEThe step id in the reference or template is not in this workflowUse this workflow's own step id.
TEMPLATE_NOT_A_REFERENCE warningA {{...}} whose first part is not a step id, so the run leaves it as textReference a step id, or keep the text deliberately.
A reference object resolves to nothingThe path is correct but the payload has no such field: a triggered run fails the step with Missing workflow mappingFix the sender's payload or the path. A defaultValue does not cover a triggered run.
A template resolves to nothingA {{nodeId.path}} whose path is missing from the payload becomes an empty string, and the run continues with it. A template whose node id is not in the workflow stays as literal textValidate before deploying: REFERENCE_UNRESOLVABLE reports a template that names no step in the workflow, and REFERENCE_UNKNOWN_FIELD reports a path its step does not produce. Neither is visible at run time.

7. After deployment

A binding that resolves in a dry run can still meet a payload it did not expect. Read the member's own field value after the first real run: a run result proves the step ran, and the member's value proves it wrote what you meant.

What the API does not cover

  • Resolving a bare node.field string for you. It is text by design; the API does not guess.
  • Copying a reference from another workflow. Ids are per workflow, so a copied reference is unresolved and validation reports it.
  • Choosing a field that does not exist on the member. That is a platform field question, not a workflow one.

Endpoints on this page