# Update a step

Changes a step's name, options, or failure behaviour.

- Endpoint: `PATCH https://api.returning.ai/v1/workflow-builder/data-workflows/{workflowId}/nodes/{nodeId}`
- Section: Workflow Builder / Steps
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `workflowBuilder`
- Retries: A retry with the same `If-Match` is safe: if the first attempt landed, the second is refused with `409 VERSION_CONFLICT` and changes nothing.
- Guide: generated from the published specification
- Last updated: 28 Oct 2026
- Web page: https://docs.returning.ai/api-reference/workflow-builder/update-a-step

## Authentication

- Header: `Authorization: Bearer <API_KEY>`
- Permission: `workflowBuilder`

`Authorization: Bearer <API_KEY>` with `workflowBuilder`, on a workflow of the key's community. `If-Match` is optional. Send the `revision` you read to avoid overwriting a concurrent change (a stale value is refused with `409 VERSION_CONFLICT`); omit it and your write wins as the last one. A deployed workflow is edited with the same call: the change applies immediately.

| Failure | Meaning | Recovery |
| --- | --- | --- |
| `400 VALIDATION_FAILED` | The merged options do not match the step type | Read the type's schema from `GET /catalog`. |

## Behaviour

`options` is merged one level deep: a key you send replaces the stored value whole, and a key you omit is kept. A stored credential can be kept by echoing the redaction marker instead of the value.

A step's `type`, its position and its branch membership are not editable here. Moving a step has its own route, and changing a type is a delete plus a create.

## Request

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `workflowId` | `string` | Yes | Workflow identifier: 24 hexadecimal characters, as returned in `_id`. (`^[0-9a-fA-F]{24}$`) |
| `nodeId` | `string` | Yes | Step identifier: 24 hexadecimal characters, as returned in the step's `_id`. (`^[0-9a-fA-F]{24}$`) |

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | `string` | Yes | Community API key: `Bearer <API_KEY>`. |
| `If-Match` | `string` | No | Optional. Send the `revision` the change is based on, bare or quoted, to avoid overwriting a concurrent change: a stale value is refused with `409 VERSION_CONFLICT`. Omit it for last-write-wins. |
| `Content-Type` | `string` | Yes | Request body format. (`application/json`) |

### Body

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | No | New step name. (1-200 chars) |
| `options` | `object` | No | Step options. |
| `continueOnFailure` | `boolean` | No | Whether a failure stops the run. |
| `retryOnFailure` | `boolean` | No | Whether the step is retried on failure. |

### Watch for

- `options` merges one level deep. A key you send replaces its stored value completely, and a key you omit stays as it was. Read the step first when you need to change part of a nested object.
- A credential an import blanked stays marked as missing across a PATCH: the step keeps the list of paths it still has to fill, and a path drops off the list as soon as it holds a real value. A rename therefore cannot clear the reminder. `POST /{workflowId}/validate` reports the ones still empty as `MISSING_SECRET` warnings, and deploy proceeds either way.
- To keep one credential while changing the rest, send `["__REDACTED__"]` at that exact path. A moved path (a renamed header, a different authentication type) has no stored value and is refused.
- A stored credential follows the URL, including to a different host: the marker is restored from the value stored at the same path, so an HTTP step you repoint at another host still carries the old credential there. Read the `url` in the response before the first run on the new host, and replace the credential rather than echoing the marker if the host is not one you control.
- `type` and `order` are refused with `400 VALIDATION_FAILED`, not ignored: the body allows `name`, `options`, `continueOnFailure` and `retryOnFailure` only. Use `POST /{workflowId}/nodes/{nodeId}/move` to reorder, and delete plus create to change a type.
- An HTTP step's options are normalised on write: a `json` or `raw-data` body is stored as one template string holding the JSON text.
- A marker inside a raw body template is treated as text, not as a secret placeholder.
- A `platform-action` field key is matched against the community's real fields when the request sends `fields`: a read accepts `total_xp`, `total_coins`, `xp` and `coin`, a write accepts `xp` and `coin`, and a custom field uses its own key. A key outside the list is stored as sent and `POST /{workflowId}/validate` reports `UNKNOWN_USER_FIELD` as a warning. A PATCH that does not send `fields`, such as a rename, neither rewrites nor reports them.
- A field written here without `logic` is stored as `overwrite`, which can zero a balance. `POST /{workflowId}/validate` reports `POST_LOGIC_REQUIRED` as a warning and the deploy still proceeds, so send `logic` (`add`, `subtract`, `overwrite`) on every field you write.
- A label you send is kept exactly as sent; only a missing label is filled from the catalog. The label keys the step's result, so a downstream reference uses it, and validation reports `REFERENCE_UNKNOWN_FIELD` when a reference names a label the step does not produce.

### Example request

```bash
curl --request PATCH \
  --url 'https://api.returning.ai/v1/workflow-builder/data-workflows/<workflowId>/nodes/<nodeId>' \
  --header 'Authorization: Bearer <API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "name": "Read member XP and coins"
  }'
```

### More examples

Replace the options while keeping a stored credential. The marker at `headers.X-Api-Key` and at `auth.token` keeps both stored values:

```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: 162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c" \
  --data '{"options":{"method":"POST","url":["https://receiver.example.com/v2/trade-bonus"],"params":{},"headers":{"X-Api-Key":["__REDACTED__"]},"bodyType":"json","body":{"member":["{{65f1a2b3c4d5e6f708192a41.email}}"]},"auth":{"type":"bearer-token","token":["__REDACTED__"]}}}'
```

Make a step continue after a failure and retry once:

```bash
curl --request PATCH \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes/65f1a2b3c4d5e6f708192a3c" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --header "If-Match: 68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501" \
  --data '{"continueOnFailure":true,"retryOnFailure":true}'
```

```json
{
  "data": {
    "node": {
      "_id": "65f1a2b3c4d5e6f708192a3c",
      "name": "Read member XP and coins",
      "type": "platform-action",
      "order": 1,
      "continueOnFailure": true,
      "retryOnFailure": true,
      "options": {
        "action": "get-user-fields",
        "selectUser": { "type": "email", "value": ["{{65f1a2b3c4d5e6f708192a41.email}}"] },
        "fields": [{ "label": "Total XP", "field": "total_xp" }]
      }
    },
    "revision": "68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501",
    "redactedPaths": []
  }
}
```

## Response

HTTP `200` with the stored step and the workflow's new `revision` in the body and the `ETag` header. The response is the readback for the fields you changed; a `GET /{workflowId}` is the readback that the step is in the right place with the right branch membership.

The stored options are the merged result: a top-level key you sent is replaced whole, and a top-level key you omitted is still there. The merge is one level deep only, so a nested object you send replaces the whole nested object.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `data` | `object` | - | One step with the workflow revision it left behind. |
| `data.node` | `object` | - | A step as returned by the API. |
| `data.node._id` | `string` | - | Step identifier. |
| `data.node.name` | `string` | - | Step name. |
| `data.node.type` | `string` | - | Step type. |
| `data.node.order` | `integer` | - | Position on the main line, from 1. |
| `data.node.continueOnFailure` | `boolean` | - | Whether a failure stops the run. |
| `data.node.retryOnFailure` | `boolean` | - | Whether the step is retried on failure. |
| `data.node.options` | `object` | - | Stored step options, with every credential replaced by `["__REDACTED__"]`. (Nullable) |
| `data.node.status` | `string` | - | Last run state of the step, when one exists. |
| `data.node.nestedNodes` | `object[]` | - | Present on a router: each branch carries its own `workflowNodeIds` and `nestedNodes`. |
| `data.revision` | `string` | - | Revision after the write. |
| `data.redactedPaths` | `string[]` | - | Credential locations replaced in this response. |

### Example response (200)

```json
{
  "data": {
    "node": {
      "_id": "65f1a2b3c4d5e6f708192a3c",
      "name": "Read member XP and coins",
      "type": "platform-action",
      "order": 1,
      "continueOnFailure": false,
      "retryOnFailure": false,
      "options": {
        "action": "get-user-fields",
        "selectUser": {
          "type": "email",
          "value": ["{{65f1a2b3c4d5e6f708192a41.email}}"]
        },
        "fields": [
          {
            "label": "Total XP",
            "field": "total_xp"
          }
        ]
      }
    },
    "revision": "68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501",
    "redactedPaths": []
  }
}
```

## Errors

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 400 | `VALIDATION_FAILED` | An empty body, an unknown field such as `type` or `order`, or options that do not match the type. Send at least one allowed field. |
| 400 | `REDACTED_VALUE_WITHOUT_STORED_SECRET` | A marker arrived at a path with no stored credential, for example after switching authentication type or renaming a header. Send the real value for that path. |
| 400 | `NODE_NOT_IN_WORKFLOW` | A router's branch list names a step that is not in this workflow. Reference only steps this workflow owns. |
| 400 | `LEADERBOARD_NOT_IN_COMMUNITY` | A leaderboard step names another community's leaderboard. Use a leaderboard of the key's community. |
| 401 | `AUTHENTICATION_REQUIRED` | No key, or an unknown key. Send a valid key. |
| 401 | `API_KEY_REQUIRED` | A valid admin session was used, because this API accepts community API keys only. Use a community API key. |
| 403 | `API_KEY_PERMISSION_DENIED` | No `workflowBuilder`. Grant the permission. |
| 413 | `VALIDATION_FAILED` | 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. |
| 415 | `UNSUPPORTED_MEDIA_TYPE` | The body was not declared `application/json`. Send the header. |

### Fix the data

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `NODE_NOT_FOUND` | The step is not part of this workflow. Read the workflow for its step ids. |
| 409 | `VERSION_CONFLICT` | You sent an `If-Match` that is not the current revision. `meta.currentRevision` carries the live value. Re-read, rebuild the change, resend. |
| 409 | `WORKFLOW_LOCKED` | Another write to this workflow held the lock for too long, and this write was not applied. Re-read and retry. |

### Retry with backoff

| Status | Code | What to do |
| --- | --- | --- |
| 429 | `RATE_LIMITED` | The community's API rate limit was reached. Wait for the window to reset, then retry. |
| 500 | `INTERNAL_ERROR` | Unexpected failure. Re-read the step before retrying. |
| 503 | `LOCK_UNAVAILABLE` | The write lock store did not answer, so nothing was written. Retry shortly. |

**Retries:** A retry with the same `If-Match` is safe: if the first attempt landed, the second is refused with `409 VERSION_CONFLICT` and changes nothing. A retry with the *new* revision is a second edit, and it applies the same body again, so only resend deliberately. Keeping a stored credential by echoing the marker means a retry can never write the marker as a value: a marker at a path with no stored value is refused.

## Next step

- [Delete a step](https://docs.returning.ai/api-reference/workflow-builder/delete-a-step.md): `DELETE /v1/workflow-builder/data-workflows/{workflowId}/nodes/{nodeId}`.
