Skip to content

Workflows API reference

Publish immutable workflow versions, start and cancel runs, read block-level results and manage heal proposals through the workflow endpoints.

Last updated:

Workflows are the repeatable half of Browserberg: blocks published as immutable versions, executed inside a session you provide. Authorization is the bearer scheme the rest of the v1 surface uses — an API key after the word Bearer — with no exception among these endpoints.

POST /v1/workflows

Publishes a workflow definition and answers 201 with the new version.

Publish body

Name Type Description
workflowId string Publish a new version of an existing workflow; omit it to create one.
title required string Up to 200 characters.
parameters array Declared inputs: `{key, description?, required?, default?}` with `key` a valid identifier.
blocks required array 1 to 100 blocks; a block without `nextBlockLabel` falls through to the next in declaration order.
runSequentially boolean Serialises runs of this workflow.
sequentialKey string Scopes that serialisation to a key of your choosing.
finallyBlockLabel string A block that runs last on every path — except after a cancel.

Validation, labels, versions

Validation reports everything at once: a rejected publish carries details.errors as an array of {code, at, message} entries, so one round trip fixes the lot. Labels must be valid identifiers — Open_portal, not a phrase with spaces — because a label becomes the variable through which later blocks reference that block's output. And every accepted publish is a new immutable version: runs already started keep executing the version they were stamped with.

GET /v1/workflows

Lists your workflows at their current versions.

GET /v1/workflows/:id

Returns one workflow; add `version` as a query parameter to read an older immutable version.

POST /v1/workflows/:id/runs

Starts a run in an existing session and answers 202.

Run body

Name Type Description
sessionId required string The session the run executes in — runs never create their own.
inputs object Values for the declared parameters.
version integer Pins the run to a specific version; the default is the current one.
GET /v1/workflow-runs/:id

Returns the run with its per-block results.

GET /v1/workflow-runs

Runs across the organisation, newest first. `workflowId`, `status` (`running`, `completed`, `failed`, `canceled`) and `limit` (default 50, at most 200) filter.

Run and block fields

Name Type Description
status string `running`, then `completed`, `failed` or `canceled`.
endedBy string The `endedBy` of the block that ended the run (an agent block’s `gave_up`, `not_offered`, `budget` …), or `canceled`. Absent when the run simply completed: a workflow with no agent block was not verified by anyone. Blocks carry their own `endedBy` too.
blocks array One BlockRun per executed block: `label`, `blockType`, `status` (`completed`, `failed`, `skipped`, `canceled`), `output`, `error`, `attempts` and — inside loops — `iteration`.
outputs object The run's collected outputs, keyed by label.
DELETE /v1/workflow-runs/:id

Cancels the run — a cancel is never absorbed by a block's `continueOnFailure`.

GET /v1/workflows/:id/health

Reports the workflow's drift score and episodes; retries never multiply an episode.

GET /v1/heal-proposals

Lists heal proposals, filterable with `workflowId` and `status` query parameters.

POST /v1/heal-proposals/:id/adopt

Adopts a proposal as a new version; it carries `baseVersion` and a content hash, and only a closed list of fields on a closed list of block types can ever be proposed — never a `code` block.

POST /v1/heal-proposals/:id/reject

Rejects a proposal; publishing a new version yourself marks open proposals stale anyway.

Publish, run, poll

Publish a two-block workflow, run it in a fresh session and poll until terminal.

Requires: api-key inference

BASE="https://browserberg.com"
AUTH="Authorization: Bearer $BROWSERBERG_API_KEY"
JSON="Content-Type: application/json"

# Publish. Every publish is a new immutable version; labels must be valid
# identifiers because they become template variables.
WFID=$(curl -s -X POST "$BASE/v1/workflows" -H "$AUTH" -H "$JSON" -d '{
  "title": "Read the example heading",
  "blocks": [
    { "blockType": "navigation", "label": "Open_page", "url": "https://example.com" },
    { "blockType": "extraction", "label": "Read_heading",
      "instruction": "the page heading",
      "schema": { "type": "object", "properties": { "heading": { "type": "string" } } } }
  ]
}' | python3 -c 'import json,sys; print(json.load(sys.stdin)["workflow"]["workflowId"])')

SID=$(curl -s -X POST "$BASE/v1/sessions" -H "$AUTH" -H "$JSON" -d '{"ttlSeconds": 300}' \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["session"]["id"])')

RUNID=$(curl -s -X POST "$BASE/v1/workflows/$WFID/runs" -H "$AUTH" -H "$JSON" \
  -d "{\"sessionId\": \"$SID\"}" \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["run"]["runId"])')

# Poll until terminal.
for i in $(seq 1 90); do
  STATUS=$(curl -s "$BASE/v1/workflow-runs/$RUNID" -H "$AUTH" \
    | python3 -c 'import json,sys; print(json.load(sys.stdin)["run"]["status"])')
  if [ "$STATUS" != "running" ]; then break; fi
  sleep 2
done

echo "run: $STATUS"
curl -s "$BASE/v1/workflow-runs/$RUNID" -H "$AUTH" \
  | python3 -c 'import json,sys; print(json.dumps(json.load(sys.stdin)["run"]["outputs"], indent=2))'

curl -s -X DELETE "$BASE/v1/sessions/$SID" -H "$AUTH" > /dev/null