GoRunner docs
Search or askSearch⌘K

Publish a workflow

POST/api/workflows/{id}/publish

Makes the draft the live version and turns the workflow on.

Needs an API token with the Publish permission for workflows (workflow:publish).

Parameters

Path

idRequired · UUID

The workflow’s ID.

Body

Optional.

expectedRevOptional · integer

The definitionRev you read. If the draft changed since, nothing happens and you get 409. Left out, 0 or less: no check.

What are the rarely used parameters?
reviewOptional · object

Only for Keel’s publish card. Leave it out.

What it does

The draft is saved as a new version, and status becomes active. Live triggers run that version. Editing the draft afterwards changes nothing live until you publish again.

A production run through Run a workflow needs a published workflow.

Publishing an unchanged draft makes no new version and still answers 200, so it is safe to repeat. It takes no Idempotency-Key. It doesn’t change updatedAt. definitionRev moves every time, so take the new one from the answer before sending expectedRev again.

The webhook address

The first publish of a webhook workflow makes its webhookToken, which the answer holds if the token you call with has the Run permission for workflows. Its address, /api/webhooks/{webhookToken}, takes runs without a token: whoever has it can start production runs.

If it leaks, get a new webhook address. Publishing again also signs a self-registering trigger, such as Stripe’s, up with its service again. If that fails, the publish still goes through.

What it refuses

Any problem that Check a definition marks blocking, and also: a schedule that can’t be read, an approval link nothing waits for, an error workflow that can’t run, or an error or finished trigger watching a workflow it can’t watch.

A step whose connection is missing from the workspace answers details.code connection_not_found. One made for another service answers connection_wrong_piece. A trigger may use its action app’s connection, such as a Gmail trigger with a Gmail connection.

Request

GORUNNER="https://api.gorunner.io"
GORUNNER_TOKEN="grt_…"  # needs Publish for workflows
WORKFLOW_ID="your-workflow-id"

URL="$GORUNNER/api/workflows/$WORKFLOW_ID/publish"
curl -X POST "$URL" \
  -H "Authorization: Bearer $GORUNNER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "expectedRev": 7
  }'

Response · 200 OK, shortened

json
{
  "data": {
    "id": "3b1e5c2a-7d4f-4e8b-a1c6-5f2d9b0e7a31",
    "name": "Order intake",
    "status": "active",
    "triggerType": "webhook",
    "webhookToken": "2f4c8e1a-6b3d-4f7e-9a05-c1d2e3f4a5b6",
    "latestVersionId": "f20f671a-2c8e-4b9d-a7f1-6e3b0d5c9a12",
    "definitionRev": 7,
    "hasUnpublishedChanges": false,
    "updatedAt": "2026-10-24T16:40:00Z"
  }
}

Responses

  • 200OK

    Live. latestVersionId names the version production now runs.

  • id isn’t a UUID, or the body isn’t JSON, or expectedRev isn’t a whole number.

  • The token lacks the Publish permission for workflows.

  • Only Keel’s own publish card sends this: a write no person clicked. An API call never gets it.

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

  • expectedRev is out of date: someone saved since. Nothing changed. Read the workflow again.

  • Another write to this workflow took too long. Try again.

  • Only for Keel’s publish card: the draft or the last version moved since the card was shown. Nothing was published.

  • The draft can’t go live. The message names the step; details has code, step, stepKey, field and alternatives.

  • A step uses an app this workspace may not use. It is named before any other problem.

  • Nothing went live. Try again.

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

Any call can also answer: 401 UNAUTHORIZED · 401 TOKEN_ENDED · 403 TOKEN_SURFACE_OFF · 403 FEATURE_OFF · 403 EMBED_NOT_ALLOWED · 403 EMBED_NOT_AVAILABLE · 403 ACCOUNT_SUSPENDED · 413 PAYLOAD_TOO_LARGE · 415 INVALID_CONTENT_TYPE · 429 RATE_LIMITED · 503 BUSY · 503 PAUSED · 503 SERVICE_UNAVAILABLE. See Errors.

Response · 422 Unprocessable Entity

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "“Forward to fulfilment” names a connection this workspace doesn’t have (0f8b2c7e-…). Choose one of its HTTP Request connections, or connect HTTP Request under Connections first.",
    "details": {
      "code": "connection_not_found",
      "step": "Forward to fulfilment",
      "stepKey": "forward",
      "field": "connectionId"
    }
  }
}
Every field

Body

expectedRevOptional · integer

The definitionRev of the draft the caller holds. Makes the publish conditional: a draft that moved since gives 409 and nothing is published. Omit — or send 0 or less, which is ignored — for an unconditional publish.

reviewOptional · object

Required on a person’s click on the assistant’s publish card (X-Assistant-Chat with X-Assistant-Click), ignored otherwise: the comparison the card showed. 409 KEEL_PUBLISH_STALE when it no longer describes the workflow.

revOptional · integer

The definitionRev of the draft the card showed.

baseVersionIdOptional · string

The last version a person published, which the card compared the draft with; empty for a first publish.

Response · 200 OK

dataobject

A workflow. definition is the DRAFT — what the editor saves into. Production executes the snapshot named by latestVersionId instead, which is why hasUnpublishedChanges matters.

idUUID
namestring
descriptionstring
definitionobject

The workflow definition. It is a TREE, not a list of steps: the trigger holds nextAction, each action holds the next one, and branches/loops/error paths nest their own sub-chains. A flat actions array is not a definition and would execute nothing.

A workflow definition: its fields are in Workflow definition.

statusstring

draft = never published. active = live, triggers registered, public webhook reachable. paused = was live, toggled off.

One of draft, active, paused.

triggerTypestring

Derived from the definition’s trigger on every write. Only webhook workflows have a webhookToken.

One of manual, cron, webhook, error, run_finished, poll.

cronExpressionstring or null
webhookTokenstring or null

The workflow’s public production door. POST /api/webhooks/{webhookToken} is UNAUTHENTICATED — the unguessable URL is the credential. Minted by publish for a webhook workflow, revoked and replaced by POST /api/workflows/{id}/webhook/rotate. Present here only for a credential holding workflow:run; redacted below that, and never present in the workflow LIST.

latestVersionIdUUID or null

The published snapshot production runs. Null until first publish.

definitionRevinteger

Optimistic-lock revision, bumped on every save. Pass it back as expectedRev to make a write conditional. Present on the single-workflow read and on write responses; absent (0) in lists.

hasUnpublishedChangesboolean

True when the draft has moved ahead of what is live. Computed in SQL, so it is exact — undoing an edit back to the published bytes reads false again.

folderIdUUID or null
tagsarray of strings
testPayloadsobject or null

Saved test fixtures. Stored outside the definition, so publish/restore never resets them.

pinnedDataobject or null

Frozen step outputs replayed instead of executing, TEST runs only. Stored outside the definition.

workspaceIdUUID
orgIdUUID
createdByUUID or null
createdAttime
updatedAttime