PATCH /{workflowId}/nodes/{nodeId} needs workflowBuilder.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.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.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. |
headers.X-Api-Key and at auth.token keeps both stored values:{
"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": []
}
}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.| Status and code | When | Recovery |
|---|---|---|
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. |
403 API_KEY_PERMISSION_DENIED | No workflowBuilder | Grant the permission. |
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. |
415 UNSUPPORTED_MEDIA_TYPE | The body was not declared application/json | Send the header. |
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. |
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.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.POST /{workflowId}/validate reports the ones still empty as MISSING_SECRET warnings, and deploy proceeds either way.["__REDACTED__"] at that exact path. A moved path (a renamed header, a different authentication type) has no stored value and is refused.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.json or raw-data body is stored as one template string holding the JSON text.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.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.REFERENCE_UNKNOWN_FIELD when a reference names a label the step does not produce.POST /{workflowId}/nodes/{nodeId}/test with mode: "dry-run" to check the changed step without side effects.GET /{workflowId}/nodes/{nodeId} to confirm the stored options.POST /{workflowId}/validate before deploying.curl --location --request PATCH 'https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/nodes/65f1a2b3c4d5e6f708192a3c' \
--header 'Authorization: Bearer <API_KEY>' \
--header 'If-Match: 162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c' \
--header 'Content-Type: application/json' \
--data '{
"name": "Read member XP and coins"
}'{
"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": []
}
}