# List versions

**GET** `/api/workflows/{id}/versions`

Returns a workflow’s published versions, newest first, without their definitions.

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

## Parameters

### Path

- `id` · Required · UUID: The workflow’s ID.

### Query

- `page` · Optional · integer: The page, from 1.
- `limit` · Optional · integer: Versions a page, 1 to 100. The default is 20; anything else reads as 20.

## Read a row

Each publish that changed something makes one version. Publishing the same tree again makes none.

`change` says what changed since the version before, by step name; its `summary` is one line: “Added Log it · Edited Fetch customers”. `changeMessage` is empty: nothing writes it. `publishedBy` is missing when a token, not a person, published it.

`onCanvas` marks the one version, if any, that matches the draft. The live one is the workflow’s `latestVersionId`. For a version’s tree, [get the version](https://docs.gorunner.io/api/publish/get-workflow-version).

## Responses

- 200 OK

  A page of versions. None answers `[]`.

- 400 `VALIDATION_ERROR`

  `id` isn’t a UUID.

- 403 `FORBIDDEN`

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

- 404 `NOT_FOUND`

  No workflow with this ID in the token’s workspace.

- 500 `INTERNAL_ERROR`

  The versions couldn’t be read. 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) · [`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 Read for workflows
WORKFLOW_ID="your-workflow-id"

URL="$GORUNNER/api/workflows/$WORKFLOW_ID/"
URL="${URL}versions?limit=20"
curl "$URL" \
  -H "Authorization: Bearer $GORUNNER_TOKEN"
```

**JavaScript**

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

const res = await fetch(
  `${GORUNNER}/api/workflows/${WORKFLOW_ID}/` +
    `versions?limit=20`,
  {
    headers: { Authorization: `Bearer ${TOKEN}` },
  },
);
const { data } = await res.json();
```

**Python**

```python
import os, requests

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

res = requests.get(
    f"{GORUNNER}/api/workflows/{WORKFLOW_ID}/"
    f"versions",
    params={"limit": 20},
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]
```

Response · 200 OK

```json
{
  "data": [
    {
      "id": "f20f671a-2c8e-4b9d-a7f1-6e3b0d5c9a12",
      "workflowId": "3b1e5c2a-7d4f-4e8b-a1c6-5f2d9b0e7a31",
      "versionNumber": 3,
      "publishedBy": "5d8e1f3a-0b6c-4e2d-9f7a-1c3b5e8d2a40",
      "changeMessage": "",
      "createdAt": "2026-10-24T16:41:00Z",
      "onCanvas": true,
      "change": {
        "changed": [
          "Forward to fulfilment"
        ],
        "summary": "Edited Forward to fulfilment"
      }
    },
    {
      "id": "1d7f3b9e-2c4a-4e6b-8d0f-5a3c9e1b7d26",
      "workflowId": "3b1e5c2a-7d4f-4e8b-a1c6-5f2d9b0e7a31",
      "versionNumber": 2,
      "publishedBy": "5d8e1f3a-0b6c-4e2d-9f7a-1c3b5e8d2a40",
      "changeMessage": "",
      "createdAt": "2026-10-22T10:05:00Z",
      "onCanvas": false,
      "change": {
        "changed": [
          "Order received"
        ],
        "summary": "Edited Order received"
      }
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 2,
    "totalPages": 1
  }
}
```

**Every field**

It takes no body.

### Response · 200 OK

- `data` · array of objects
  - `id` · UUID
  - `workflowId` · UUID
  - `versionNumber` · integer: 1-based, increasing.
  - `definition` · object: OMITTED from the versions LIST; present on the single-version read. A workflow definition: its fields are in [Workflow definition](https://docs.gorunner.io/api/workflow-definition).
  - `publishedBy` · UUID: Who published it (a user id). Absent — never null — when a machine principal or the dev principal published it: a version names a person or nobody.
  - `changeMessage` · string: Kept for old clients: nothing writes it, so it is empty. `change.summary` (on the list) says what changed.
  - `createdAt` · time
  - `onCanvas` · boolean: Marks the AT MOST ONE version whose snapshot equals the current DRAFT. Computed only by the LIST (`false` on a single read). Distinct from LIVE, which is the workflow’s `latestVersionId`.
  - `change` · object: LIST only: what this version changed against the one before it, keyed on step name, as the list shows it. Absent on the single-version read, and on the last row of a page when the version before it is not in hand.
    - `first` · boolean: The first version: nothing to compare with.
    - `added` · array of strings: Steps added, by display name, in canvas order.
    - `removed` · array of strings: Steps removed, by display name.
    - `changed` · array of strings: Steps changed, by display name.
    - `trigger` · string: How the way it starts changed, in words ("Webhook replaced Schedule", "Webhook reconfigured"); absent when it did not.
    - `settings` · boolean: The workflow-level settings changed (timeout, concurrency, error workflow).
    - `summary` · string: The one-line form the row shows.
- `pagination` · object: Paging envelope returned alongside `data` on list endpoints.
  - `page` · integer: The 1-based page that was returned.
  - `limit` · integer: Rows per page actually applied (not necessarily what you asked for — out-of-range values fall back to 20).
  - `total` · integer: Total matching rows across all pages.
  - `totalPages` · integer

Related: [Get a version](https://docs.gorunner.io/api/publish/get-workflow-version) · [Go back to a version](https://docs.gorunner.io/api/publish/restore-workflow-version) · [Test, publish and go back to a version](https://docs.gorunner.io/build/test-and-publish)

Source: https://docs.gorunner.io/api/publish/list-workflow-versions
