Publish a workflow
POST/
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 · UUIDThe workflow’s ID.
Body
Optional.
expectedRevOptional · integerThe
definitionRevyou 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 · objectOnly 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
}'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}/` +
`publish`,
{
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ expectedRev: 7 }),
},
);
const { data } = await res.json();import os, requests
GORUNNER = "https://api.gorunner.io"
TOKEN = os.environ["GORUNNER_TOKEN"]
WORKFLOW_ID = os.environ["WORKFLOW_ID"]
res = requests.post(
f"{GORUNNER}/api/workflows/{WORKFLOW_ID}/"
f"publish",
headers={"Authorization": f"Bearer {TOKEN}"},
json={"expectedRev": 7},
)
data = res.json()["data"]Response · 200 OK, shortened
{
"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.
latestVersionIdnames the version production now runs. idisn’t a UUID, or the body isn’t JSON, orexpectedRevisn’t a whole number.- 403
FORBIDDENThe 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.
- 404
NOT_FOUNDNo workflow with this ID in the token’s workspace.
- 409
CONFLICTexpectedRevis out of date: someone saved since. Nothing changed. Read the workflow again. - 409
CONFLICTAnother 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;
detailshascode,step,stepKey,fieldandalternatives.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
{
"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 · integerThe
definitionRevof 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 · objectRequired on a person’s click on the assistant’s publish card (
X-Assistant-ChatwithX-Assistant-Click), ignored otherwise: the comparison the card showed. 409KEEL_PUBLISH_STALEwhen it no longer describes the workflow.revOptional · integerThe
definitionRevof the draft the card showed.baseVersionIdOptional · stringThe last version a person published, which the card compared the draft with; empty for a first publish.
Response · 200 OK
dataobjectA workflow.
definitionis the DRAFT — what the editor saves into. Production executes the snapshot named bylatestVersionIdinstead, which is whyhasUnpublishedChangesmatters.idUUIDnamestringdescriptionstringdefinitionobjectThe 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 flatactionsarray is not a definition and would execute nothing.A workflow definition: its fields are in Workflow definition.
statusstringdraft= never published.active= live, triggers registered, public webhook reachable.paused= was live, toggled off.One of
draft,active,paused.triggerTypestringDerived from the definition’s trigger on every write. Only
webhookworkflows have awebhookToken.One of
manual,cron,webhook,error,run_finished,poll.cronExpressionstring or nullwebhookTokenstring or nullThe 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 byPOST /api/workflows/{id}/webhook/rotate. Present here only for a credential holdingworkflow:run; redacted below that, and never present in the workflow LIST.latestVersionIdUUID or nullThe published snapshot production runs. Null until first publish.
definitionRevintegerOptimistic-lock revision, bumped on every save. Pass it back as
expectedRevto make a write conditional. Present on the single-workflow read and on write responses; absent (0) in lists.hasUnpublishedChangesbooleanTrue 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 nulltagsarray of stringstestPayloadsobject or nullSaved test fixtures. Stored outside the definition, so publish/restore never resets them.
pinnedDataobject or nullFrozen step outputs replayed instead of executing, TEST runs only. Stored outside the definition.
workspaceIdUUIDorgIdUUIDcreatedByUUID or nullcreatedAttimeupdatedAttime