# Export a workflow definition

Returns the workflow as a portable definition: its name, its trigger, and every step with its options.

- Endpoint: `GET https://api.returning.ai/v1/workflow-builder/data-workflows/{workflowId}/export`
- Section: Workflow Builder / Workflows
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `workflowBuilder`
- Retries: Safe to retry: nothing is written.
- Guide: generated from the published specification
- Last updated: 28 Oct 2026
- Web page: https://docs.returning.ai/api-reference/workflow-builder/export-a-workflow-definition

## Authentication

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

`Authorization: Bearer <API_KEY>` with `workflowBuilder`, for the community that owns the workflow. Redaction is not optional and has no parameter to turn it off, so an export can be read, kept and re-imported without the document ever holding a working credential.

| Failure | Meaning | Recovery |
| --- | --- | --- |
| `401 AUTHENTICATION_REQUIRED` | No key, or an unknown key | Send a valid key. |
| `403 API_KEY_PERMISSION_DENIED` | No `workflowBuilder` | Grant the permission. |
| `404 WORKFLOW_NOT_FOUND` | Unknown id, or another community's workflow | Use an id from this community. |

## Behaviour

The document has the same shape an import reads, so it can be posted back to `POST /` as `{definition}` to recreate the workflow, here or in another community.

Node credentials in the document are replaced by the redaction marker and listed in `redactedPaths`. A credential that an import blanked and nobody has filled in yet is written the same way, so an export always shows what is still missing. The trigger's own credentials are removed entirely rather than marked: a webhook trigger's key stays behind as `apiKey: {enabled: true}` with no value, and an AES key arrives as `enabled` with an empty key and IV. Import mints or re-collects those.

## Request

### Path parameters

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

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | `string` | Yes | Community API key: `Bearer <API_KEY>`. |

### Watch for

- Redaction covers HTTP auth values, credential-named headers, parameters and body keys, URL query parameters with a credential name, and node-level credentials. The trigger's key material is not exported at all. A secret an author typed inside a raw body template is returned as authored, so treat an export as sensitive even though the named credentials are replaced.
- An empty auth value is "no auth", not a redacted secret: a bearer token stored as `[]` stays `[]` and is not listed in `redactedPaths`.
- Importing this document blanks the redacted node credentials, reports them in `secretsToFill`, and `POST /{workflowId}/validate` reports each one as a `MISSING_SECRET` warning until it is filled in. The warning does not stop a deploy, so fill them before going live.
- The trigger's key does not travel, so an import mints a fresh webhook key for the new workflow. An AES key cannot be minted: it arrives enabled but blank, for the author to re-enter.
- The export has no run history, no identifiers from the source community's fields or leaderboards beyond the ids the steps already stored, and no deployment state.

### Example request

```bash
curl --request GET \
  --url 'https://api.returning.ai/v1/workflow-builder/data-workflows/<workflowId>/export' \
  --header 'Authorization: Bearer <API_KEY>'
```

### More examples

A workflow with one HTTP step whose headers and bearer token are redacted:

```json
{
  "kind": "returningai.workflow",
  "formatVersion": 1,
  "variant": "data_workflows",
  "exportedAt": "2026-10-24T11:02:00.000Z",
  "workflow": { "name": "VIP bonus", "outgoingWebhookUrl": null },
  "trigger": {
    "name": "Trade bonus trigger",
    "type": "webhook",
    "options": { "tokenExchangeEnabled": false, "apiKey": { "enabled": true } }
  },
  "nodes": [
    {
      "ref": "node-0",
      "name": "Notify receiver",
      "type": "http",
      "order": 1,
      "continueOnFailure": false,
      "retryOnFailure": false,
      "options": {
        "method": "POST",
        "url": ["https://receiver.example.com/trade-bonus"],
        "params": {},
        "headers": { "X-Api-Key": ["__REDACTED__"] },
        "bodyType": "json",
        "body": { "member": ["{{trigger.email}}"] },
        "auth": { "type": "bearer-token", "token": ["__REDACTED__"] }
      }
    }
  ],
  "redactedPaths": ["nodes[0].options.headers.X-Api-Key", "nodes[0].options.auth.token"]
}
```

## Response

HTTP `200` with the document at the top level, not wrapped in `data`: it is a file format, and an import reads it verbatim. `redactedPaths` rides alongside the document.

`kind` is `returningai.workflow`, `formatVersion` is `1`, and `variant` is `data_workflows`. All four are required by an import.

The document carries one id space: step references and `{{...}}` templates are rewritten to the document's own names, `trigger` for the trigger and each step's `ref` for the steps, so the same file can be imported into any community without editing a reference. Each reference is paired with the step it named rather than with a position, so a step inserted after the trigger or a step inside a router branch keeps its meaning. An import rewires them onto the ids it mints. Step order is authored order, and `ref` is the local name a template or reference can use.

To prove an export, import it and compare: create a workflow with `{definition}`, then read the new workflow back and match step types, order and options, ignoring identifiers.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `kind` | `string` | - | Document kind. |
| `formatVersion` | `integer` | - | Document format version. |
| `variant` | `string` | - | Which workflow runtime produced the document. An import requires `data_workflows`. |
| `exportedAt` | `string` | - | When the document was produced. |
| `workflow` | `object` | - | Workflow name and failure webhook. |
| `trigger` | `object` | - | Trigger name, type and options, with credentials replaced. (Nullable) |
| `nodes` | `object[]` | - | Steps in canvas order, with credentials replaced. Step references and `{{...}}` templates use the document's own names: `trigger` and each step's `ref`. |
| `nodes.ref` | `string` | - | Local reference name. |
| `redactedPaths` | `string[]` | - | Every location replaced by the marker. |

### Example response (200)

```json
{
  "kind": "returningai.workflow",
  "formatVersion": 1,
  "variant": "data_workflows",
  "exportedAt": "2026-10-24T11:02:00.000Z",
  "workflow": {
    "name": "VIP bonus",
    "outgoingWebhookUrl": null
  },
  "trigger": {
    "name": "Trade bonus trigger",
    "type": "webhook",
    "options": {
      "tokenExchangeEnabled": false,
      "apiKey": {
        "enabled": true
      }
    }
  },
  "nodes": [
    {
      "ref": "node-0",
      "name": "Notify receiver",
      "type": "http",
      "order": 1,
      "continueOnFailure": false,
      "retryOnFailure": false,
      "options": {
        "method": "POST",
        "url": ["https://receiver.example.com/trade-bonus"],
        "params": {},
        "headers": {
          "X-Api-Key": ["__REDACTED__"]
        },
        "bodyType": "json",
        "body": {
          "member": ["{{trigger.email}}"]
        },
        "auth": {
          "type": "bearer-token",
          "token": ["__REDACTED__"]
        }
      }
    }
  ],
  "redactedPaths": [
    "nodes[0].options.headers.X-Api-Key",
    "nodes[0].options.auth.token"
  ]
}
```

## Errors

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 400 | `VALIDATION_FAILED` | The id is not 24 hexadecimal characters. Use the id exactly as returned. |
| 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. |

### Fix the data

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `WORKFLOW_NOT_FOUND` | Unknown id, or another community's workflow. Use an id from this community. |

### 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. Retry with bounded backoff. |

**Retries:** Safe to retry: nothing is written. The document contains `exportedAt`, so two exports of an unchanged workflow differ in that field only.

## Next step

- [Create or replace the trigger](https://docs.returning.ai/api-reference/workflow-builder/create-or-replace-the-trigger.md): `PUT /v1/workflow-builder/data-workflows/{workflowId}/trigger`.
