Returning.AIDocs
v1

API reference / Workflow Builder

.md

Update a step

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

Last updated 28 Oct 2026API v1

Method
PATCH
Path
https://api.returning.ai/v1/workflow-builder/data-workflows/{workflowId}/nodes/{nodeId}
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.

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.

FailureMeaningRecovery
400 VALIDATION_FAILEDThe merged options do not match the step typeRead 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#

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>

If-Match#stringOPTIONAL
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.

Eg162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c

Content-Type#stringREQUIRED
Request body format.

Ruleapplication/json

Body#

name#stringOPTIONAL
New step name.

Rule1-200 chars

Eg"Read member XP and coins"

options#objectOPTIONAL
Step options.

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

continueOnFailure#booleanOPTIONAL
Whether a failure stops the run.

Egfalse

retryOnFailure#booleanOPTIONAL
Whether the step is retried on failure.

Egfalse

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.
More examples and details

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": []
  }
}
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"
  }'

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.

data#object
One step with the workflow revision it left behind.

Eg{ ... }

node#object
A step as returned by the API.

Eg{ ... }

_id#string
Step identifier.

Eg"65f1a2b3c4d5e6f708192a3c"

name#string
Step name.

Eg"Read member XP and coins"

type#string
Step type.

Eg"platform-action"

order#integer
Position on the main line, from 1.

Eg1

continueOnFailure#boolean
Whether a failure stops the run.

Egfalse

retryOnFailure#boolean
Whether the step is retried on failure.

Egfalse

options#object
Stored step options, with every credential replaced by ["__REDACTED__"].

RuleNullable

Eg{ ... }

status#string
Last run state of the step, when one exists.

Eg"success"

nestedNodes#object[]
Present on a router: each branch carries its own workflowNodeIds and nestedNodes.
revision#string
Revision after the write.

Eg"68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501"

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

Eg[]

{
  "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 request09

400VALIDATION_FAILEDFix the request
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.
400REDACTED_VALUE_WITHOUT_STORED_SECRETFix the request
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.
400NODE_NOT_IN_WORKFLOWFix the request
A router's branch list names a step that is not in this workflow. Reference only steps this workflow owns.
400LEADERBOARD_NOT_IN_COMMUNITYFix the request
A leaderboard step names another community's leaderboard. Use a leaderboard of the key's community.
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
No workflowBuilder. 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 data03

404NODE_NOT_FOUNDFix the data
The step is not part of this workflow. Read the workflow for its step ids.
409VERSION_CONFLICTFix the data
You sent an If-Match that is not the current revision. meta.currentRevision carries the live value. Re-read, rebuild the change, resend.
409WORKFLOW_LOCKEDFix the data
Another write to this workflow held the lock for too long, and this write was not applied. Re-read and retry.

Retry with backoff03

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. Re-read the step before retrying.
503LOCK_UNAVAILABLERetry with backoff
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.

{
  "meta": {
    "status": "error",
    "statusCode": 400,
    "code": "VALIDATION_FAILED",
    "errors": [
      {
        "path": "node.options.url",
        "message": "URL is required"
      }
    ],
    "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#

Delete a stepDELETE/v1/workflow-builder/data-workflows/{workflowId}/nodes/{nodeId}