Edit safely: revisions, conflicts, export, import and copying a workflow
Most workflow incidents are not logic bugs. They are a save that silently overwrote someone else's edit, a change that went live because nobody noticed the workflow was deployed, or a workflow copied to another community that still pointed at the first community's fields.
The API removes most of the guesswork: every write can be revision-checked, every response is the stored state, and a copy gets new identifiers. What it does not do is stop you: the checks are opt-in, so the routine below is what actually keeps a live workflow safe. This page is the safe editing routine, and it replaces the recovery workarounds that existed before the API.
What the API makes unnecessary
| Old workaround | Why it is no longer needed |
|---|---|
| Reading a save response and hoping it landed | Every write returns the stored object; a readback is one call |
| Manual re-read before an edit to dodge a race | Sending If-Match refuses a stale write instead of overwriting it |
| Guessing whether a workflow is deployed | status is in every read. A write to a deployed workflow is allowed and applies immediately, so read the status before you change a live one |
| Exporting to see whether a credential leaked into a file | The export redacts credentials and lists where |
| Rebuilding a copied workflow by hand | POST /{workflowId}/duplicate copies steps, trigger and references |
| Comparing screenshots to see what changed | Two reads compared by revision answer whether anything changed |
The one thing the API does not replace is a person deciding whether a change is wanted.
1. Read before you write
curl --request GET \
--url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b" \
--header "Authorization: Bearer <API_KEY>"
Keep revision, status, the step ids and the options of the step you mean to change. Note which credentials appear in redactedPaths: those are the values you must echo back rather than retype.
2. Make the smallest change
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 '{"name":"Notify receiver (v2)"}'
PATCH on a step replaces options wholesale but leaves fields you omit alone, so send only what changes. The response is the stored step and the new revision.
3. Handle a conflict instead of retrying through it
A 409 VERSION_CONFLICT means someone else changed the workflow, usually a person in the canvas. meta.currentRevision is the live revision.
- Re-read with
GET /{workflowId}. - Compare the step you meant to change with what you sent.
- Rebuild the change on the new revision.
- Send it again with the new
If-Match.
Do not resend the old body with the new revision without looking: that is how one editor's change is silently undone by another's.
Nothing refuses a write to a live workflow. The API does not ask whether you meant it: a change to a deployed workflow takes effect for the next run, so read status in step 1, and use the copy routine below when the change deserves a review.
4. The safe change to a live workflow
POST /{workflowId}/duplicateto get a draft copy with new ids.PATCHandPOST /{workflowId}/nodeson the copy until it is what you want.POST /{workflowId}/validateandPOST /{workflowId}/teston the copy.POST /{workflowId}/deployon the copy, thenPOST /{workflowId}/undeployon the original.- Keep the original as the rollback.
A duplicate copies the failure webhook URL and the credentials, so check both on the copy before deploying it: two live workflows reporting to one receiver, or sharing one webhook key, is usually not what you want. There is no rotation call: to give the copy its own key, set its trigger to another type and then webhook again, which mints a new one, and reconfigure the copy's sender.
5. Export before a risky change
curl --request GET \
--url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/export" \
--header "Authorization: Bearer <API_KEY>" \
--output workflow-backup.json
The document is the portable definition with every credential replaced by the marker. It is the rollback plan: if the change goes wrong, import it as a new workflow. An export is still sensitive, because a secret an author typed inside a raw body template is returned as authored.
6. Copy a workflow to another community
Export from the source community, then import into the target with a key from the target community.
curl --request POST \
--url "https://api.returning.ai/v1/workflow-builder/data-workflows" \
--header "Authorization: Bearer <API_KEY_OF_THE_TARGET_COMMUNITY>" \
--header "Content-Type: application/json" \
--data '{"name":"Tier sync (copy)","definition":{"kind":"returningai.workflow","formatVersion":1,"variant":"data_workflows","workflow":{"name":"Tier sync"},"trigger":null,"nodes":[]}}'
Before the import, edit the file:
- Set the workflow name.
- Replace every credential with the marker, or delete it: the import blanks node credentials and reports them in
secretsToFill. - Blank every saved
defaultValuein selector and value slots. An exported default can be a real customer identifier or a reward amount. - Check the destination community has every field and role the steps write, with the same key and type. A field key that exists in the source community may not exist in the target: read the target's
GET /catalogand compare itsplatformActionFieldswith the keys in the export before importing.
After the import:
- The response lists
secretsToFill. Supply each one withPATCH /{workflowId}/nodes/{nodeId}. The list is kept on the server and survives a PATCH, including a rename, and each path drops out as soon as it holds a real value, so filling one credential does not clear the reminder for the rest. A later read does not show the marker itself, and an export writes the still-empty paths as["__REDACTED__"], which is how you see what is left. POST /{workflowId}/validatereportsMISSING_SECRETfor any that are still empty. That is a warning, not a refusal: the deploy goes ahead with the credential empty, so fill them first.PUT /{workflowId}/triggerto give the copy its own trigger. A webhook trigger created there mints its own key; a copy that kept the source's webhook trigger keeps the source's key until you set another type and back.- Deploy only if the source was deployed, and only after a
mode: "dry-run"test. - Re-export the copy and compare it with the file you imported, ignoring identifiers and the new trigger key.
Import creates a new workflow with new identifiers and rewires the same references a duplicate does: reference objects, field values that name a step, router branch lists, and {{stepId.path}} templates. The pairing is by step identity rather than position, so an insert after the trigger or a step inside a router branch still binds to the step it meant, and a template that points at the source's trigger or steps lands on the imported ones. Validate the copy anyway, because a reference to something outside the document has nowhere to point. The two communities' workflows can still share an external destination: an HTTP step's URL survives the import, so check every outside call before deploying the copy.
7. Failure handling
| What you see | What it means | What to do |
|---|---|---|
409 VERSION_CONFLICT | Another writer got there first | Re-read, rebuild the change, resend. |
| A change landed on a live workflow that you meant to stage | The API does not gate edits to a deployed workflow | Use the copy routine, and read status before every write. |
400 REDACTED_VALUE_WITHOUT_STORED_SECRET | A marker arrived where nothing is stored | Send the real value. |
| An HTTP step now calls the wrong host, still carrying a stored credential | The marker is restored by path, so the credential follows the URL | Repoint the step and replace the credential rather than echoing the marker. |
MISSING_SECRET after an import | A credential the import blanked is still empty | Patch that step with a real value. |
| The copy behaves differently from the source | A field, role or identifier the target community does not have | Compare the steps and the target's field keys. |
| The canvas editor shows unsaved changes on an API-built step | The editor fills a default the API did not store | Save deliberately in the canvas, then re-read and compare. |
8. Cleanup
Delete the copy you no longer need once the original is retired, and delete the source only after the copy has run in the target. A delete works on a deployed workflow too, so undeploy first if you want the runs to stop before the workflow goes.
What the API does not cover
- Replacing a whole existing workflow with an import. Import creates a new workflow; the old one is deleted or undeployed separately.
- Moving a step between branches in one call. That is a delete plus a create.
- Reviewing a change before it reaches members. Nothing in the API holds a change back: a write to a deployed workflow applies at once, so a review has to be a duplicate and a deploy of the copy.
- Deciding whether a change is wanted. A conflict exits so a person can look, but only if you sent
If-Match.
Endpoints on this page
- Get a workflow:
GET /v1/workflow-builder/data-workflows/{workflowId} - Update a step:
PATCH /v1/workflow-builder/data-workflows/{workflowId}/nodes/{nodeId} - Export a workflow definition:
GET /v1/workflow-builder/data-workflows/{workflowId}/export - Create a workflow:
POST /v1/workflow-builder/data-workflows - Duplicate a workflow:
POST /v1/workflow-builder/data-workflows/{workflowId}/duplicate - Add a step:
POST /v1/workflow-builder/data-workflows/{workflowId}/nodes - Validate a stored workflow:
POST /v1/workflow-builder/data-workflows/{workflowId}/validate - Test a whole workflow:
POST /v1/workflow-builder/data-workflows/{workflowId}/test - Deploy a workflow:
POST /v1/workflow-builder/data-workflows/{workflowId}/deploy - Undeploy a workflow:
POST /v1/workflow-builder/data-workflows/{workflowId}/undeploy - List step types and trigger types:
GET /v1/workflow-builder/data-workflows/catalog - Create or replace the trigger:
PUT /v1/workflow-builder/data-workflows/{workflowId}/trigger