Use Browser Flow when a job has a bounded, known set of Humanized REST actions and you want one authenticated HTTP request to own their serial execution. The v1 wire format is a flat single-session DAG: entryStepId selects the first step, and every step declares an exact onSuccess transition plus an optional onFailure transition.
The workflow below opens one browser, navigates, reads page markdown, and converges both the success and failure paths on an explicit close. Session-bound action inputs do not include sessionId; Browser Flow injects the one effective session. Read the actual session ID from the response envelope’s effectiveSessionId.
const apiKey = process.env.BROWSERCITY_API_KEY;if (!apiKey) { throw new Error('Set BROWSERCITY_API_KEY before running this script.');}const request = { version: '1', entryStepId: 'open', steps: [ { id: 'open', action: 'browser_open', input: { browser: 'chromium', resourceTier: 'm', labels: ['template:browser-flow'], }, onSuccess: { stepId: 'navigate' }, onFailure: { end: true }, }, { id: 'navigate', action: 'browser_navigate', input: { url: 'https://example.com' }, onSuccess: { stepId: 'read' }, onFailure: { stepId: 'close' }, }, { id: 'read', action: 'browser_markdown', input: {}, onSuccess: { stepId: 'close' }, onFailure: { stepId: 'close' }, }, { id: 'close', action: 'browser_close', input: {}, onSuccess: { end: true }, onFailure: { end: true }, }, ],} as const;const response = await fetch('https://api.browser.city/v1/do/flow', { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(request),});if (!response.ok) { throw new Error(`Browser Flow was rejected before execution: ${response.status} ${await response.text()}`);}const flow = await response.json();console.log({ status: flow.status, sessionId: flow.effectiveSessionId });for (const step of flow.steps) { console.log(step.id, step.status, step.executionIndex ?? step.reason);}
Add exact typed references
A $flowRef is an exact wrapper, not a string template. Its path is an array of property names and nonnegative array indexes rooted at public step data. For example, a later browser_type step can use successful markdown text:
{
"id": "copyText",
"action": "browser_type",
"input": {
"element": "Notes field",
"ref": "notes",
"text": {
"$flowRef": {
"stepId": "read",
"path": ["result", "text"]
}
}
},
"onSuccess": { "stepId": "close" },
"onFailure": { "stepId": "close" }
}
A step reached through read.onFailure can instead read read’s public error:
{
"$flowRef": {
"stepId": "read",
"path": ["error", "message"]
}
}
The compiler proves that the referenced source is a predecessor on the selected outcome, that the path exists, and that its value is assignable to the target input. References cannot read internal session, provider, billing, credential, or private error-cause data.
Interpret outcomes correctly
All valid flows that begin execution return HTTP 200:
succeeded: execution reached an end transition without an action failure.completed_with_failures: at least one action failure followed a declaredonFailurepath and execution still reached an end transition.failed: an action failure was unhandled, or a Flow runtime/result limit stopped execution.aborted: the caller aborted the request.
Malformed JSON, authentication failure, schema/compile failure, request-body rejection, existing-session admission failure, rate admission, and same-session execution conflict are non-2xx route errors. Response step records remain in declaration order; executionIndex records the actual serial order, while skipped records explain whether a branch was not selected or the Flow stopped.
Browser Flow is deliberately non-transactional. Completed browser effects remain applied, and a successfully opened session remains open after a later failure or abort unless browser_close completed. V1 has no loops, predicates or expressions, parallel steps, multiple sessions, durable/resumable execution, rollback, compensating actions, or Flow-level retries.
The required request field version: '1' complements the /v1/do/flow URL; it does not replace URL versioning. Non-breaking additions may remain under /v1, while breaking transition, reference, status, ordering, or execution changes require a new versioned prefix and migration path.