GoRunner

Heartbeat presence and maintain the advisory editing lock

Requires scope workflow:read
POST
/api/workflows/{id}/presence

One poll that does three things: records that you have this workflow open, maintains or acquires the ADVISORY editing lock, and carries the take-over handshake — all in a single round trip.

The lock is advisory. It does not refuse writes; the real guards are expectedRev (optimistic locking) and the per-flow write lock the server takes around every mutation. Its purpose is to let a client show "someone else is editing" and go read-only.

Presence evaporates on its own: stop beating and you disappear within about 30 seconds, so a closed tab needs no cleanup. Beat at beatEverySeconds from the response — the server owns the interval, do not hard-code one.

mine is computed server-side. Never compare user ids yourself: sessions and machine principals make that comparison wrong.

The takeover handshake:

  • "request" — ask the current holder to hand over. The holder sees it in their next beat's takeover. If they do not respond, the requester can claim the lock after a ~30-second grace, so an away holder can never strand a workflow.
  • "grant" — sent by the HOLDER to hand the lock over now. There is deliberately no "decline": the service has none, and offering one would be a lie.
  • "force" — the admin override. It is checked HERE against the caller's workspace:manage permission, never trusted from the client; without it the call is 403 (note: 403 for this operation is a permission check on the takeover value, not on workflow:read).
  • "" or omitted — an ordinary beat.

editing: true means "I am an editor surface" and asks for the lock when it is free. editing: false is a passive viewer that only registers presence.

Authorization

bearerAuth
AuthorizationBearer <token>

An API token: Authorization: Bearer grt_…. Mint one in the app under Settings → Workspace → API tokens; the raw value is shown once. Scopes confine the token — see x-permission on each operation.

In: header

Path Parameters

id*string

Workflow id (UUID). A malformed id answers 404 here, not 400 — presence never discloses whether a flow exists.

Formatuuid

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/api/workflows/497f6eca-6276-4993-bfeb-53cbbbba6f08/presence" \  -H "Content-Type: application/json" \  -d '{}'
{  "data": {    "editors": [      {        "userId": "string",        "name": "string",        "since": "2019-08-24T14:15:22Z",        "keel": true      }    ],    "lock": {      "userId": "string",      "name": "string",      "since": "2019-08-24T14:15:22Z",      "keel": true    },    "mine": true,    "takeover": {      "userId": "string",      "name": "string",      "since": "2019-08-24T14:15:22Z"    },    "beatEverySeconds": 0  }}