Building workflows from blocks
Compose navigation, actions, extraction and control flow into a versioned workflow: nine block types, immutable versions and labels that become variables.
Last updated:
A line is already a graph
Blocks, versions and labels
A workflow is a list of blocks. When a block has no nextBlockLabel, execution falls through to the next block in declaration order — so a simple sequence is the graph in which every edge is the obvious one, and you only draw an edge when you mean a branch.
Every publish creates a new immutable version, and a run executes the version it started with; publishing mid-run changes nothing already in flight. Block labels must be valid identifiers (Read_heading, not a phrase with spaces) because a label becomes a template variable: a later block can reference {{Read_heading_output.heading}}.
The nine block types
The type column names the field each block is built around.
| Name | Type | Description |
|---|---|---|
navigation
|
url | Opens a URL. The simplest edge of the graph. |
action
|
instruction | An instruction, optionally with a composed-XPath selector. aiFallback chooses whether the model acts proactively or only once the selector fails (proactive|fallback). |
extraction
|
instruction | Pulls structured data from the page, optionally against a JSON Schema. |
login
|
credentialId | Signs in with a vault credential. The block carries a credential id, never a secret. |
validation
|
criterion | Checks a criterion against the live page and fails the run when it does not hold. |
code
|
code | Customer JavaScript. It runs in the session container, never in the platform process. |
http_request
|
method, url | Calls an external API from the run and hands the response to later blocks. |
loop
|
over | Repeats nested blocks over a list, bounded by maxIterations. |
conditional
|
branches | Branches on criteria. All branch criteria are answered in one model call against one snapshot; the first true branch wins. |
Publish, run, poll
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
run: completed
{
"Open_page_output": {
"url": "https://example.com"
},
"Read_heading_output": {
"heading": "Example Domain"
}
}
Sequencing, cleanup, validation
For portals that permit one session per login, set runSequentially with a sequentialKey: runs sharing the key queue up instead of evicting each other's sign-in. finallyBlockLabel names a block that runs last on every completed or failed run — with one deliberate exception: it does not run on a cancel, because a cancel means stop now, not stop after more steps.
When you publish something invalid, the API returns all validation errors at once, each with a code and a position — one round trip to fix the whole definition, not one per mistake.