# Import a workflow

**POST** `/api/workflows/import`

Makes a draft workflow from an export: GoRunner’s, or another automation tool’s.

Needs [an API token](https://docs.gorunner.io/api/authentication#scopes) with the **Write** permission for workflows (`workflow:write`).

## Parameters

### Headers

- `Idempotency-Key` · Optional · string: Up to 255 characters. The same request with the same key within 24 hours answers the first result again instead of importing a second copy. See Idempotency.

### Body

The exported file, as it is: GoRunner’s envelope, the whole answer of Export a workflow, or just its `workflow` object; or a workflow exported from another automation tool. Up to 5 MiB.

- `gorunnerExport` · Optional · string: In GoRunner’s envelope: always `workflow`.
- `version` · Optional · integer: In GoRunner’s envelope: always `1`.
- `workflow` · Optional · object: In GoRunner’s envelope: the name, description, tags and definition. Empty name: “Imported Workflow”. A name over 255 characters is shortened, and tags past the limits are cut, rather than refused.

## What carries over

The import is a new draft with a new ID. It gets none of the original’s traffic: it has no webhook address until you first update it or publish it.

Every app and action must exist here. In a GoRunner export, a step without a `pieceVersion` is pinned to the app’s oldest version, the one it always ran; write the version on a definition of your own (a new Code step: `2.0.0`). An unfinished draft imports fine.

A field that held a secret arrives empty: the export has only `[hidden secret: kept as set]`.

Connection IDs come over as they are. They don’t exist on another installation, so point each step at a connection there.

To see first what another tool’s export becomes, [preview the import](https://docs.gorunner.io/api/workflows/preview-workflow-import).

## The report

Each node that runs in the original file is listed once in `importReport.nodes`, with its `outcome`:

`mapped`: an app here does the job. `substituted`: a general app does the same work, such as HTTP Request. `parked`: nothing here does it, so the step is a No Op holding the original settings, to replace.

`ready` says whether the step runs as imported. `leftovers` counts settings that had nowhere to go. `needsAttention` counts steps that can’t run yet.

The steps, their order and names, branches and their conditions, loops, an HTTP call’s method and address, a schedule and a code step’s code carry over. A trigger that can’t be read exactly becomes manual. Notes on the canvas are counted in `warnings`.

## Responses

- 201 Created

  The new draft, with its `definitionRev`. From another tool’s export, it also has `importReport`.

- 400 `VALIDATION_ERROR`

  The body isn’t JSON, the tree has a key it doesn’t take (the message names it), a step’s name or display name is over 255 characters, or a condition has an operator that doesn’t exist (the message lists the ones that do).

- 403 `FORBIDDEN`

  The token lacks the **Write** permission for workflows.

- 409 `CONFLICT`

  A request with this `Idempotency-Key` is still in progress, or its answer was too large to keep. Try again shortly.

- 422 `VALIDATION_ERROR`

  The file isn’t an export GoRunner reads (the message says what it found), it holds no definition, or `schemaVersion` is something other than 2, 0 or absent.

- 422 `VALIDATION_ERROR`

  A step names an app or action this installation doesn’t have. The message names it.

- 422 `VALIDATION_ERROR`

  This `Idempotency-Key` was already used with a different request.

- 422 `PIECE_NOT_ALLOWED`

  A GoRunner export has a step on an app this workspace may not use. Another tool’s export never answers this: such steps are parked.

- 500 `INTERNAL_ERROR`

  Nothing was imported. Try again.

- 503 `PIECES_UNAVAILABLE`

  GoRunner couldn’t check which apps this workspace may use. Nothing changed. Try again.

Any call can also answer: [`401 UNAUTHORIZED`](https://docs.gorunner.io/errors#UNAUTHORIZED) · [`401 TOKEN_ENDED`](https://docs.gorunner.io/errors#TOKEN_ENDED) · [`403 TOKEN_SURFACE_OFF`](https://docs.gorunner.io/errors#TOKEN_SURFACE_OFF) · [`403 FEATURE_OFF`](https://docs.gorunner.io/errors#FEATURE_OFF) · [`403 EMBED_NOT_ALLOWED`](https://docs.gorunner.io/errors#EMBED_NOT_ALLOWED) · [`403 EMBED_NOT_AVAILABLE`](https://docs.gorunner.io/errors#EMBED_NOT_AVAILABLE) · [`403 ACCOUNT_SUSPENDED`](https://docs.gorunner.io/errors#ACCOUNT_SUSPENDED) · [`413 PAYLOAD_TOO_LARGE`](https://docs.gorunner.io/errors#PAYLOAD_TOO_LARGE) · [`415 INVALID_CONTENT_TYPE`](https://docs.gorunner.io/errors#INVALID_CONTENT_TYPE) · [`429 RATE_LIMITED`](https://docs.gorunner.io/errors#RATE_LIMITED) · [`503 BUSY`](https://docs.gorunner.io/errors#BUSY) · [`503 PAUSED`](https://docs.gorunner.io/errors#PAUSED) · [`503 SERVICE_UNAVAILABLE`](https://docs.gorunner.io/errors#SERVICE_UNAVAILABLE). See [Errors](https://docs.gorunner.io/api/errors#by-status).

Request

**curl**

```bash
GORUNNER="https://api.gorunner.io"
GORUNNER_TOKEN="grt_…"  # needs Write for workflows

curl -X POST "$GORUNNER/api/workflows/import" \
  -H "Authorization: Bearer $GORUNNER_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "gorunnerExport": "workflow",
    "version": 1,
    "workflow": {
      "name": "Order intake",
      "tags": [
        "orders"
      ],
      "definition": {
        "schemaVersion": 2,
        "trigger": {
          "name": "trigger",
          "type": "webhook",
          "pieceId": "webhook",
          "actionId": "catch_webhook",
          "config": {
            "methods": [
              "POST"
            ]
          },
          "valid": true
        },
        "settings": {}
      }
    }
  }'
```

**JavaScript**

```js
const GORUNNER = "https://api.gorunner.io";
const TOKEN = process.env.GORUNNER_TOKEN;

const res = await fetch(
  `${GORUNNER}/api/workflows/import`,
  {
    method: "POST",
    headers: {
        Authorization: `Bearer ${TOKEN}`,
        "Idempotency-Key": crypto.randomUUID(),
        "Content-Type": "application/json",
      },
    body: JSON.stringify({
        gorunnerExport: "workflow",
        version: 1,
        workflow: {
          name: "Order intake",
          tags: ["orders"],
          definition: {
            schemaVersion: 2,
            trigger: {
              name: "trigger",
              type: "webhook",
              pieceId: "webhook",
              actionId: "catch_webhook",
              config: {
                methods: ["POST"],
              },
              valid: true,
            },
            settings: {},
          },
        },
      }),
  },
);
const { data } = await res.json();
```

**Python**

```python
import os, uuid, requests

GORUNNER = "https://api.gorunner.io"
TOKEN = os.environ["GORUNNER_TOKEN"]

res = requests.post(
    f"{GORUNNER}/api/workflows/import",
    headers={
        "Authorization": f"Bearer {TOKEN}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
      "gorunnerExport": "workflow",
      "version": 1,
      "workflow": {
        "name": "Order intake",
        "tags": ["orders"],
        "definition": {
          "schemaVersion": 2,
          "trigger": {
            "name": "trigger",
            "type": "webhook",
            "pieceId": "webhook",
            "actionId": "catch_webhook",
            "config": {
              "methods": ["POST"],
            },
            "valid": True,
          },
          "settings": {},
        },
      },
    },
)
data = res.json()["data"]
```

Response · 201 Created, shortened, from another tool’s export

```json
{
  "data": {
    "id": "6e9a1c3f-8b2d-4a57-9e0c-3f7b5d1a8c42",
    "name": "Order intake",
    "status": "draft",
    "definitionRev": 1,
    "triggerType": "webhook",
    "importReport": {
      "format": "external",
      "name": "Order intake",
      "nodes": [
        {
          "sourceName": "HTTP Request",
          "sourceType": "httpRequest",
          "outcome": "mapped",
          "pieceId": "http",
          "stepName": "http_request",
          "ready": true,
          "leftovers": 1
        }
      ],
      "mappedCount": 2,
      "substitutedCount": 0,
      "parkedCount": 0,
      "needsAttention": 0,
      "stepsWithLeftovers": 1
    }
  }
}
```

**Every field**

The body takes one of 2 forms.

### Body, an export file, as Export a workflow gives it

- `gorunnerExport` · Required · string: Format marker; the value is the literal string `workflow`, not a boolean. One of `workflow`.
- `version` · Required · integer: Export FORMAT version — 1. Unrelated to `definition.schemaVersion`, which is 2. One of `1`.
- `exportedAt` · Optional · time: When the export was produced. Informational; import ignores it.
- `workflow` · Required · object: The portable payload inside an export envelope. Import also accepts this object on its own, without the envelope around it.
  - `name` · Optional · string: On import, an empty or absent name becomes "Imported Workflow".
  - `description` · Optional · string
  - `triggerType` · Optional · string: Overridden by whatever `definition.trigger` says, so it is effectively informational. One of `manual`, `cron`, `webhook`, `callable`, `event`, `error`, `run_finished`, `poll`.
  - `tags` · Optional · array of strings: Trimmed and deduplicated case-insensitively on import.
  - `definition` · Required · object: The workflow definition, and the single most important shape in this API. A workflow definition: its fields are in [Workflow definition](https://docs.gorunner.io/api/workflow-definition).

### Body, the workflow alone, without the envelope

- `name` · Optional · string: On import, an empty or absent name becomes "Imported Workflow".
- `description` · Optional · string
- `triggerType` · Optional · string: Overridden by whatever `definition.trigger` says, so it is effectively informational. One of `manual`, `cron`, `webhook`, `callable`, `event`, `error`, `run_finished`, `poll`.
- `tags` · Optional · array of strings: Trimmed and deduplicated case-insensitively on import.
- `definition` · Required · object: The workflow definition, and the single most important shape in this API. A workflow definition: its fields are in [Workflow definition](https://docs.gorunner.io/api/workflow-definition).

### Response · 201 Created

- `data` · object
  - `id` · UUID
  - `name` · string
  - `description` · string
  - `definition` · object: The workflow definition, and the single most important shape in this API. A workflow definition: its fields are in [Workflow definition](https://docs.gorunner.io/api/workflow-definition).
  - `status` · string: `draft` = never published. `active` = live, triggers registered and the webhook URL reachable. `paused` = was live, now switched off. Server-owned: it moves only through publish/unpublish, never through a create or update body. One of `draft`, `active`, `paused`.
  - `triggerType` · string: Mirrors `definition.trigger.type` for dispatch. Derived server-side from the definition by every update and publish, so treat it as read-only in practice. A create that sends its own value keeps it until then, unchecked. One of `manual`, `cron`, `webhook`, `callable`, `event`, `error`, `run_finished`, `poll`.
  - `cronExpression` · string or null: Nothing derives or reads this: the schedule runs from the trigger’s own `config`, and `nextRunAt` projects from it too. Null unless a caller wrote one with `PUT /api/workflows/{id}`.
  - `webhookToken` · string or null: The secret segment of the public webhook URL. Made by the first update of a webhook workflow, or at publish; absent until then (so absent in the answers of create, duplicate and import). Returned ONLY to a caller holding `workflow:run`: by `GET /api/workflows/{id}` and in the answers of update, publish, unpublish and restore (rotate answers it to its caller). It is never present on a list row, an export or a version snapshot. It is a bearer capability against an unauthenticated endpoint — possessing it is the ability to fire this workflow in production. Rotate it with `POST /api/workflows/{id}/webhook/rotate`.
  - `latestVersionId` · UUID or null: The published snapshot production executes. Absent means nothing has ever been published.
  - `definitionRev` · integer: Optimistic-lock revision, bumped by every definition save. Populated by `GET /api/workflows/{id}`, by the update response and by the answers of create, duplicate and import; absent (0) on list rows. Send it back as `expectedRev` on an update to refuse a clobbering write.
  - `hasUnpublishedChanges` · boolean: The draft has moved ahead of the published version. Computed on the single-workflow read and on every list row; always `false` for a workflow never published.
  - `folderId` · UUID or null: Absent/null = uncategorized. Changed only through `PUT /api/workflows/{id}/folder`.
  - `tags` · array of strings: Changed only through `PUT /api/workflows/{id}/tags` or the bulk `addTags` operation — never through the workflow update body.
  - `testPayloads` · object or null: The saved test-fixture library, stored outside the definition so publish and version restore never reset it.
  - `pinnedData` · object or null: The pin library (`{"pins": {"<stepName>": {"data": …, "pinnedAt": …, "configHash": …, "staleAt": …, "pieceId": …}}}`). A pinned step emits its fixture instead of executing, and only on test runs. `configHash`, `staleAt` and `pieceId` are the server’s: a step whose config changed keeps its pin marked stale, and a stale pin is not replayed (the step runs); a step whose app was replaced loses it.
  - `workspaceId` · UUID: Stamped from the credential’s scope; never accepted from a request body.
  - `orgId` · UUID
  - `createdBy` · UUID or null
  - `createdByName` · string: List rows only: the creator’s name, else their email address. Absent when the creator is unknown or not a member of the organization (staff who built it on a support visit, someone since removed), and on every row for an embedded person whose installation switched `presence` off (an embedded person is never told an address).
  - `createdAt` · time
  - `updatedAt` · time: When the workflow itself was last edited. Publishing, pausing, tagging and filing it are not edits and leave it alone.
  - `failing` · boolean: List rows only: the workflow is live (`status: active`) and its latest FINISHED production run (one that worked or failed) failed. A later run that is waiting, running, held or was cancelled changes nothing, and test runs never count. A paused or draft workflow is never failing.
  - `failingSince` · time: Only on a failing row: when the first production run failed after the last one that worked (runs cancelled, held or running in between neither break nor begin the streak). Left out for a caller without `run:read`.
  - `failingRun` · object: Only on a failing row: the run that decided it, its latest finished production run, with why it failed. It is `lastRun` unless a later run is waiting, running, held or was cancelled. Without `run:read` it carries `status` and `startedAt` alone.
    - `id` · UUID
    - `status` · string: One of `failed`.
    - `startedAt` · time
    - `finishedAt` · time or null
    - `error` · object: Why it failed, as `lastRun.error` says it.
      - `step` · string or null
      - `stepKey` · string or null
      - `message` · string
  - `summary` · object: List rows only: what a list draws instead of the definition.
    - `trigger` · object or null: The chosen trigger; null while none is chosen.
      - `pieceId` · string
      - `actionId` · string
      - `displayName` · string
    - `pieces` · array of objects: The apps the workflow uses, each once, in walk order (the trigger first), with their display names.
      - `id` · string
      - `name` · string
    - `stepCount` · integer: Steps after the trigger, across branch arms, loop bodies and error paths.
    - `needsSetup` · integer: Steps (and a chosen trigger) the editor marks to configure: `valid: false`, or no app and action chosen.
  - `runs7d` · array of integers: List rows without `view=summary` only: counted runs per UTC day over the last seven days, oldest first (production runs that are not a sub-flow’s, cancelled runs left out).
  - `nextRunAt` · time: Next fire time of an active schedule workflow, projected from its cron in the trigger’s own timezone. Populated by the list endpoint.
  - `lastRun` · object or null: The most recent PRODUCTION run’s outcome (test runs never count). Populated on list rows only. Without `run:read` it carries `status` and `startedAt` alone.
    - `id` · UUID
    - `finishedAt` · time or null
    - `error` · object: Only when `status` is `failed`: why.
      - `step` · string or null: The display name of the step it failed at; null when the reason names no step.
      - `stepKey` · string or null
      - `message` · string: The reason’s first clause, at most 160 characters.
    - `status` · string: One of `pending`, `running`, `completed`, `failed`, `cancelled`, `paused`.
    - `startedAt` · time
  - `importReport` · object: What a translation from another product could and could not carry. Every node of the source file that asks to RUN appears in `nodes` exactly once; canvas comments (sticky notes and other notes) are counted in `warnings` instead, since they are not steps. One bound, reported when it is reached: branches and loops nested deeper than this product’s own structural limit are not followed, because the alternative is letting a file decide how deep the server recurses. Real flows nest a handful of levels.
    - `format` · string: Detected from the file: always `external` (another automation tool’s export). One of `external`.
    - `name` · string: The name the source file gave the workflow.
    - `nodes` · array of objects
      - `sourceName` · string: What the source file called it, so a person can find it there.
      - `sourceType` · string: The source node’s own short type, without the source tool’s package prefix, e.g. `slack` or `httpRequest`.
      - `outcome` · string: `mapped` — an app here does that job. `substituted` — no app for it, but a generic one does the same work. `parked` — nothing here does it, so the step is an invalid No Op carrying the original settings. One of `mapped`, `substituted`, `parked`.
      - `pieceId` · string: The GoRunner app it became. `no-op` on a parked step.
      - `stepName` · string: Its name here — what an expression referencing it says.
      - `ready` · boolean: Whether the step runs as imported. False asks for a visit: a connector can be MAPPED and still need its fields, because a foreign node’s settings rarely mean the same thing here.
      - `note` · string: The honest sentence for this row.
      - `leftovers` · integer: How many of the source’s own settings are still on this node because there was no field for them. Present even when `ready` is true — a step can run and still be unfinished.
    - `mappedCount` · integer
    - `substitutedCount` · integer
    - `parkedCount` · integer
    - `needsAttention` · integer: How many steps cannot run as imported — the number that decides whether this is a draft to finish or a draft to publish.
    - `stepsWithLeftovers` · integer: How many nodes still hold settings the translation could not place, whether or not they can run. NOT the same question as needsAttention: a step can run exactly as imported and still be carrying settings the original had, so a flow can be fully valid and still have translation left in it.
    - `warnings` · array of strings: File-level things the reader should know: a shape that had to be flattened (the source ran a fan-out in parallel, this product runs one chain), nodes nothing reached, comments left behind, expressions that need rewriting.

Related: [Preview an import](https://docs.gorunner.io/api/workflows/preview-workflow-import) · [Export a workflow](https://docs.gorunner.io/api/workflows/export-workflow) · [Import from n8n or Activepieces](https://docs.gorunner.io/build/import-workflows)

Source: https://docs.gorunner.io/api/workflows/import-workflow
