# 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`](https://docs.returning.ai/api-reference/workflow-builder/list-step-types-and-trigger-types.md), 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.

> A wrong key or label is not 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`](https://docs.returning.ai/api-reference/workflow-builder/validate-a-stored-workflow.md) 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`](https://docs.returning.ai/api-reference/workflow-builder/list-step-types-and-trigger-types.md) lists them in `platformActionFields.get` (read) and `platformActionFields.post` (write). See [Field keys come from the catalog](https://docs.returning.ai/workflow-builder.md#field-keys-come-from-the-catalog).

`Get a workflow`

```bash
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`](https://docs.returning.ai/api-reference/workflow-builder/validate-a-stored-workflow.md) 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`](https://docs.returning.ai/api-reference/workflow-builder/validate-a-stored-workflow.md) 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.

`Add a step`

```bash
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:

`Update a step`

```bash
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

`Test a whole workflow`

```bash
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}`](https://docs.returning.ai/api-reference/workflow-builder/get-a-step.md): 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 shows | Likely cause | Fix |
| --- | --- | --- |
| `wouldDo.users` empty, step `failed` with `User not found` | The identity is text, or the field path is wrong for this trigger | Fix the selector's `nodeId` and `field`. |
| The written value is the mapping expression | The value slot holds a bare string | Replace it with a template or a reference object. |
| `REFERENCE_NOT_UPSTREAM` from validate | The step reads a step that runs after it, or a sibling branch | Move the step, or read an earlier step. |
| `REFERENCE_MAY_BE_UNSET` warning | The step reads a branch step that may not have run | Accept it, or move the read into the same branch. |
| `REFERENCE_UNRESOLVED` | The id belongs to another workflow | Use this workflow's own step id. |
| `REFERENCE_UNKNOWN_FIELD` | The reference names a label the step does not produce | Use the label on that step's `fields[]`, or the catalog's label for the key. |
| `REFERENCE_SHAPE_MISMATCH` | The reference reads a field off a number or a list | Read the value whole, or read a field of an object result. |
| `REFERENCE_UNRESOLVABLE` | The step id in the reference or template is not in this workflow | Use this workflow's own step id. |
| `TEMPLATE_NOT_A_REFERENCE` warning | A `{{...}}` whose first part is not a step id, so the run leaves it as text | Reference a step id, or keep the text deliberately. |
| A reference object resolves to nothing | The path is correct but the payload has no such field: a triggered run fails the step with `Missing workflow mapping` | Fix the sender's payload or the path. A `defaultValue` does not cover a triggered run. |
| A template resolves to nothing | A `{{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 text | Validate 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

- GET [Get a workflow](https://docs.returning.ai/api-reference/workflow-builder/get-a-workflow.md) `/{workflowId}`
- POST [Add a step](https://docs.returning.ai/api-reference/workflow-builder/add-a-step.md) `/{workflowId}/nodes`
- PATCH [Update a step](https://docs.returning.ai/api-reference/workflow-builder/update-a-step.md) `/{workflowId}/nodes/{nodeId}`
- POST [Test a whole workflow](https://docs.returning.ai/api-reference/workflow-builder/test-a-whole-workflow.md) `/{workflowId}/test`
- GET [List step types and trigger types](https://docs.returning.ai/api-reference/workflow-builder/list-step-types-and-trigger-types.md) `/catalog`
- POST [Validate a stored workflow](https://docs.returning.ai/api-reference/workflow-builder/validate-a-stored-workflow.md) `/{workflowId}/validate`
- GET [Get a step](https://docs.returning.ai/api-reference/workflow-builder/get-a-step.md) `/{workflowId}/nodes/{nodeId}`

Paths that start with `/v1/` are full paths; the rest are relative to `https://api.returning.ai/v1/workflow-builder/data-workflows`.
