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

`Get a workflow`

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

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

1. Re-read with [`GET /{workflowId}`](https://docs.returning.ai/api-reference/workflow-builder/get-a-workflow.md).
2. Compare the step you meant to change with what you sent.
3. Rebuild the change on the new revision.
4. 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

1. [`POST /{workflowId}/duplicate`](https://docs.returning.ai/api-reference/workflow-builder/duplicate-a-workflow.md) to get a draft copy with new ids.
2. `PATCH` and [`POST /{workflowId}/nodes`](https://docs.returning.ai/api-reference/workflow-builder/add-a-step.md) on the copy until it is what you want.
3. [`POST /{workflowId}/validate`](https://docs.returning.ai/api-reference/workflow-builder/validate-a-stored-workflow.md) and [`POST /{workflowId}/test`](https://docs.returning.ai/api-reference/workflow-builder/test-a-whole-workflow.md) on the copy.
4. [`POST /{workflowId}/deploy`](https://docs.returning.ai/api-reference/workflow-builder/deploy-a-workflow.md) on the copy, then [`POST /{workflowId}/undeploy`](https://docs.returning.ai/api-reference/workflow-builder/undeploy-a-workflow.md) on the original.
5. Keep the original as the rollback.

> Check the copy's webhook URL and credentials
>
> 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

`Export a workflow definition`

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

`Create a workflow`

```bash
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 `defaultValue` in 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 /catalog`](https://docs.returning.ai/api-reference/workflow-builder/list-step-types-and-trigger-types.md) and compare its `platformActionFields` with the keys in the export before importing.

After the import:

1. The response lists `secretsToFill`. Supply each one with [`PATCH /{workflowId}/nodes/{nodeId}`](https://docs.returning.ai/api-reference/workflow-builder/update-a-step.md). 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.
2. [`POST /{workflowId}/validate`](https://docs.returning.ai/api-reference/workflow-builder/validate-a-stored-workflow.md) reports `MISSING_SECRET` for any that are still empty. That is a warning, not a refusal: the deploy goes ahead with the credential empty, so fill them first.
3. [`PUT /{workflowId}/trigger`](https://docs.returning.ai/api-reference/workflow-builder/create-or-replace-the-trigger.md) to 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.
4. Deploy only if the source was deployed, and only after a `mode: "dry-run"` test.
5. 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.

## 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. |

## 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
>
> 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 [Get a workflow](https://docs.returning.ai/api-reference/workflow-builder/get-a-workflow.md) `/{workflowId}`
- PATCH [Update a step](https://docs.returning.ai/api-reference/workflow-builder/update-a-step.md) `/{workflowId}/nodes/{nodeId}`
- GET [Export a workflow definition](https://docs.returning.ai/api-reference/workflow-builder/export-a-workflow-definition.md) `/{workflowId}/export`
- POST [Create a workflow](https://docs.returning.ai/api-reference/workflow-builder/create-a-workflow.md) `/`
- POST [Duplicate a workflow](https://docs.returning.ai/api-reference/workflow-builder/duplicate-a-workflow.md) `/{workflowId}/duplicate`
- POST [Add a step](https://docs.returning.ai/api-reference/workflow-builder/add-a-step.md) `/{workflowId}/nodes`
- POST [Validate a stored workflow](https://docs.returning.ai/api-reference/workflow-builder/validate-a-stored-workflow.md) `/{workflowId}/validate`
- POST [Test a whole workflow](https://docs.returning.ai/api-reference/workflow-builder/test-a-whole-workflow.md) `/{workflowId}/test`
- POST [Deploy a workflow](https://docs.returning.ai/api-reference/workflow-builder/deploy-a-workflow.md) `/{workflowId}/deploy`
- POST [Undeploy a workflow](https://docs.returning.ai/api-reference/workflow-builder/undeploy-a-workflow.md) `/{workflowId}/undeploy`
- GET [List step types and trigger types](https://docs.returning.ai/api-reference/workflow-builder/list-step-types-and-trigger-types.md) `/catalog`
- PUT [Create or replace the trigger](https://docs.returning.ai/api-reference/workflow-builder/create-or-replace-the-trigger.md) `/{workflowId}/trigger`

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