haltija

Haltija API Reference

Auto-generated from src/api-schema.ts - Do not edit directly.

Quick Start

# Is it working?
curl localhost:8700/status

# What tabs are connected?
curl localhost:8700/windows

# What's on the page?
curl -X POST localhost:8700/tree -d '{"mode":"actionable"}'

# Click something
curl -X POST localhost:8700/click -d '{"selector":"#submit"}'

Connection & Status

GET /status

Server status

Returns server info and connected browser count.

Response: { serverVersion, ready, windows: […], browsers: n, desktopApp, pid, … }

Use to verify the server is running — but gate a test lane on ready, not on the 200. A server can be up with zero connected tabs: /status answers fine and there is still nothing to drive, so a lane that adopts it fails later on a timeout that points at the caller’s own code. ready is true when at least one top-level tab is connected. hj doctor checks this (plus ambiguous targeting) and exits non-zero, which is the one-command preflight for a lane.


GET /stats

Efficiency and usage statistics

Returns metrics showing Haltija’s efficiency vs raw DOM events.

Response includes:

Use this to verify efficiency claims (99%+ event reduction, etc.) and debug performance.


GET /version

[Deprecated] Use /status instead

Deprecated: Version is included in /status response.


GET /docs

[Deprecated] Use /api instead

Deprecated: Use /api for complete API documentation.


GET /api

Full API reference

Complete API documentation with all endpoints.

Returns structured JSON with all endpoints, their parameters, and examples.


See the Page

POST /tree

Get DOM tree structure

Returns hierarchical view of page elements. Best for understanding page structure before interacting.

Response structure: { tag, id?, classes?, attrs?, text?, value?, checked?, children?, flags?: { interactive, hidden, hasAria, … } }

Flags help identify interactive elements (buttons, inputs) and hidden content. Form inputs include live value/checked state (not just HTML attribute).

Use ancestors:true to see parent elements when inspecting deep elements.

Parameters:

Name Type Description
selector string,null Root element selector
depth number,null Max depth (-1 = unlimited). Default: unlimited
includeText boolean,null Include text content (default true)
visibleOnly boolean,null Only visible elements (default false)
interactiveOnly boolean,null Only interactive elements and their ancestors (default false)
pierceShadow boolean,null Pierce shadow DOM (default true)
pierceFrames boolean,null Pierce same-origin iframes (default true)
compact boolean,null Minimal output (default false)
ancestors boolean,null Include ancestor path from root (default false)
includeBox boolean,null Include each node’s bounding box { x, y, w, h, visible } (default false)
mode string,null ‘actionable’ returns a page action summary (url, title, headings, buttons, links, inputs, selects) instead of the DOM tree
window string,null Target window ID

Examples:


POST /query

Query DOM elements by selector

Quick element lookup. Returns basic info: tagName, id, className, textContent, attributes.

Use this to check if an element exists before clicking/typing. For detailed info, use /inspect instead.

Response: { tagName, id, className, textContent, attributes: {…} }

Parameters:

Name Type Description
ref string,null Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency
selector string,null CSS selector
all boolean,null Return all matches (default false = first only)

Examples:


POST /inspect

Deep inspection of an element

Get everything about ONE element: geometry, computed styles, ARIA attributes, scroll position, visibility state.

Response includes:

Use before clicking to verify element is visible and enabled.

Parameters:

Name Type Description
ref string,null Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency
selector string,null CSS selector
fullStyles boolean,null Include all computed styles (default: false)
matchedRules boolean,null Include matched CSS rules with specificity (default: false)
window string,null Target window ID

Examples:


POST /inspectAll

Inspect multiple elements

Deep inspection of ALL elements matching selector (up to limit).

Same detailed info as /inspect, but for multiple elements. Great for:

Response: array of inspection objects

Parameters:

Name Type Description
ref string,null Ref ID from /tree output - returns single element as array
selector string,null CSS selector
limit number,null Max elements (default 10)
fullStyles boolean,null Include all computed styles (default: false)
matchedRules boolean,null Include matched CSS rules with specificity (default: false)
window string,null Target window ID

Examples:


POST /find

Find elements by text content

Search for elements containing specific text. Saves writing querySelector + filter patterns.

Returns first match by default, or all matches with all:true.

Response: { found: true, selector: “…”, element: {…} } or { found: true, elements: […] }

Parameters:

Name Type Description
text string Text to search for (substring match) (required)
tag string,null Limit to specific tag (button, a, div, etc)
exact boolean,null Require exact text match (default false = substring)
all boolean,null Return all matches (default false = first only)
visible boolean,null Only visible elements (default true)
window string,null Target window ID

Examples:


POST /form

Extract all form values as structured JSON

Get all form field values without needing to know the component’s API.

Introspects forms and returns structured data:

Response: { fields: { name: value, … }, form: { action, method, id } }

Works with standard forms and most framework components (React, Vue, etc).

Parameters:

Name Type Description
selector string,null Form selector (default: first form on page)
includeDisabled boolean,null Include disabled fields (default false)
includeHidden boolean,null Include hidden fields (default false)
window string,null Target window ID

Examples:


POST /map

Affordance map — what can be interacted with, and what it is wired to

Returns a map of the page’s affordances. Two tiers, and the difference matters:

Native (source: "tosi-agent") — EXPERIMENTAL. haltija detects this tier by duck-typing one method and consumes a shape tosijs has not committed to (no version/capability marker yet — see tosijs#23). When the shape isn’t what haltija expects it returns a warning and agentSurfaceVersion rather than passing a blank map off as a success, but treat the tier as best-effort. When the page exposes an agent surface at globalThis.tosiAgent (a tosijs app calling enableAgentInterface()), the map is the app’s OWN wiring records. That carries what the DOM cannot: which state path each control is bound to and in which direction two-way (user-writable), bound-to-DOM (display only), absent (static) — plus the handler path each event calls, and the list of callable actions.

With that you can act through paths instead of synthesized input: hj eval "tosiAgent.write('app.filter', 'milk')" or tosiAgent.call('app.addItem').

Fallback (source: "dom") — any other page, reconstructed from tags/roles/labels/state, each node carrying a haltija ref for hj click <ref>. Deliberately approximate: it has NO binding provenance, because that information does not exist in the DOM. Always check source before trusting the map as wiring rather than as a guess.

Cheaper and more stable than a screenshot for deciding what to do next: no fonts, themes, viewport or animation timing, and structure (nesting) is carried for free.

A node may carry smallTarget — an interactive control below WCAG 2.5.8’s 24x24 CSS px minimum (44x44 is the AAA / Apple / Material recommendation), reported as e.g. "16x16 (WCAG 2.5.8 needs 24x24; 44x44 recommended)" and drawn as an amber bar along the bottom edge of the schematic. Inline links inside a sentence are exempt, per the spec — they are sized by their text, and flagging them would fire on every paragraph.

With image and file (both default true), the response carries legendPath: a sibling *.legend.json written beside the image, holding a flat ref -> facts index of the ref-bearing nodes. The image says WHERE and WHICH, the legend says WHAT — which is what makes it safe to stop cramming captions into boxes too small to hold them. Read the number off the picture, look it up there.

A node may carry zeroSize: true — a real, operable control that occupies no box. The standard accessible pattern for file inputs and custom checkboxes is a 0x0 <input> driven by a <label>, so its coordinates are meaningless and clicking it directly may do nothing: click the associated <label> instead. Genuinely hidden elements (display:none, hidden ancestor) are excluded entirely, so zeroSize always means operable but invisible, never not there.

Where the walk STOPS — an element with no children may not be empty. A childless node in nodes reads exactly like a genuinely empty element, and the two are worth telling apart:

So: when a node looks empty and you expected content, reach for hj tree --shadow --frames (which pierces more) or hj map --image (which shows you the box that is there).

Parameters:

Name Type Description
global string,null Global to probe for the agent surface (default ‘tosiAgent’)
maxNodes number,null Cap on DOM-fallback nodes (default 400)
image boolean,null Also render the map as a schematic PNG (rasterized). NOTE: response.nodes is a TREE (recurse into .children — a <ul> holds its <li>s, a custom element holds its shadow content) while response.legend is a FLAT ref->facts index of only the ref-bearing nodes, so the two legitimately contain different sets. Vision cost scales with PIXELS, roughly (w*h)/750 for Claude — there is NO fixed floor, so a small or size-capped schematic can be very cheap (a 491x480 one is ~314 tokens; with maxWidth 200, ~52). ~1600 is the practical ceiling, since larger images are downscaled before tokenisation. response.cost reports approxJsonTokens and approxImageTokens for THIS page — compare those rather than assuming.
scale number,null Device-pixel scale for the schematic image (default 1). Raise it to make the captions legible to a vision model on a dense page.
layout string,null Schematic layout: ‘auto’ (default), ‘geometric’ (boxes at true page coordinates) or ‘structural’ (nested stacked boxes). ‘auto’ picks geometric when most nodes carry bounds — which they do for a DOM map, but never for the tosi-agent tier, which describes wiring and has no geometry. The response reports which was used in layout and the evidence in boundsCoverage.
fullPage boolean,null Draw the WHOLE document rather than just the viewport (default false). The schematic is laid out at real page coordinates, so a long page becomes a tall thin strip — 1126x22304 is a 1:20 ratio that no downscaling makes readable. Ask for it only when you need the parts that are off screen.
maxWidth number,null Max width in pixels for the schematic image (aspect ratio preserved)
maxHeight number,null Max height in pixels for the schematic image (aspect ratio preserved)
format string,null Schematic image format: png (default), webp, or jpeg
quality number,null Quality for lossy formats (webp/jpeg). Either scale works: 0-1 (canvas-native) or 0-100 (percentage); anything above 1 is read as a percentage and clamped. Ignored for png.
file boolean,null With image: save the PNG under /haltija-schematics and return its path in `path` (default true). Schematics older than 24h (and beyond the most recent 200) are pruned automatically. Pass false for a base64 data URL — note that is ~700KB of stdout and earns no vision-token discount unless something turns it back into an image.
window string,null Target window ID

Examples:


Do Things

POST /click

Click an element

Scrolls element into view, then performs full click sequence: mouseenter, mouseover, mousedown, mouseup, click.

Three ways to target elements:

Options:

Automatically fails if element is not found or is disabled. Check response.success to verify.

Parameters:

Name Type Description
ref string,null Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency
selector string,null CSS selector of element to click
text string,null Text content to find (alternative to selector)
tag string,null Tag name when using text (default: any clickable element)
autoWait boolean,null Wait for element to appear before clicking (default false)
timeout number,null Max wait time in ms when autoWait is true (default 5000)
diff boolean,null Return DOM diff showing what changed after click (default false)
diffDelay number,null Wait ms before capturing “after” state (default 100)
window string,null Target window ID

Examples:


POST /type

Type text into an element

Focus element and type text character by character with realistic event lifecycle.

Target element via ref (from /tree output) or selector.

Simulates real user behavior:

  1. Focus via mouse click (default) or keyboard Tab
  2. Full keystroke events: keydown → beforeinput → input → keyup
  3. Fires change event on completion

Handles native inputs, textareas, contenteditable, and framework-wrapped inputs (React, MUI, etc).

Options:

Parameters:

Name Type Description
ref string,null Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency
selector string,null CSS selector of input/textarea/contenteditable
text string Text to type (required)
autoWait boolean,null Wait for element to appear before typing (default false)
timeout number,null Max wait time in ms when autoWait is true (default 5000)
humanlike boolean,null Human-like delays and typos (default true)
focusMode string,null How to focus: mouse (default), keyboard, or direct
clear boolean,null Clear existing content before typing (default false)
blur boolean,null Blur element after typing to trigger change event (default true)
typoRate number,null Typo probability 0-1 (default 0.03)
minDelay number,null Min ms between keys (default 50)
maxDelay number,null Max ms between keys (default 150)
diff boolean,null Return DOM diff showing what changed after typing (default false)
diffDelay number,null Wait ms before capturing “after” state (default 100)
window string,null Target window ID

Examples:


POST /key

Send keyboard input

Send key press with full event lifecycle: keydown → keypress → beforeinput → input → keyup.

Target element defaults to document.activeElement. Use ref or selector to focus a specific element first.

Supports modifiers (ctrlKey, shiftKey, altKey, metaKey) and repeat count for holding keys.

Common keys: Enter, Escape, Tab, ArrowUp/Down/Left/Right, Backspace, Delete, Home, End, PageUp/PageDown, F1-F12, or any printable character.

Parameters:

Name Type Description
key string Key to press (e.g., “Enter”, “Escape”, “a”, “ArrowDown”) (required)
ref string,null Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency
selector string,null Element to focus first (default: activeElement)
ctrlKey boolean,null Hold Ctrl/Control
shiftKey boolean,null Hold Shift
altKey boolean,null Hold Alt/Option
metaKey boolean,null Hold Meta/Command
repeat number,null Repeat count for key hold (default 1)
window string,null Target window ID

Examples:


POST /drag

Drag from an element

Simulates drag gesture: mousedown on element, mousemove by delta, mouseup.

Good for: sliders, resize handles, drag-and-drop reordering, range inputs.

Parameters:

Name Type Description
ref string,null Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency
selector string,null CSS selector of drag handle
deltaX number,null Horizontal distance in pixels
deltaY number,null Vertical distance in pixels
duration number,null Drag duration in ms (default 300)
window string,null Target window ID

Examples:


POST /highlight

Visually highlight an element

Draw attention to an element with colored border and optional label.

Great for showing users what you found or pointing out issues. Use /unhighlight to remove.

Parameters:

Name Type Description
ref string,null Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency
selector string,null CSS selector
label string,null Label text to show
color string,null CSS color (default #6366f1)
duration number,null Auto-hide after ms (omit for manual)
window string,null Target window ID

Examples:


POST /unhighlight

Remove highlight

Remove any active highlight overlay created by /highlight.


POST /scroll

Scroll to element or position

Smooth scroll with natural easing. Multiple modes:

At least one of ref, selector, x, y, deltaX, or deltaY must be provided.

Parameters:

Name Type Description
ref string,null Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency
selector string,null CSS selector to scroll into view
x number,null Absolute X position in pixels
y number,null Absolute Y position in pixels
deltaX number,null Relative horizontal scroll in pixels
deltaY number,null Relative vertical scroll in pixels
duration number,null Animation duration in ms (default 500)
easing string,null Easing function: ease-out (default), ease-in-out, linear
block string,null Vertical alignment: center (default), start, end, nearest
window string,null Target window ID

Examples:


POST /wait

Wait for time, element, or condition

Flexible wait for async UI scenarios. Multiple modes:

All modes support timeout (default 5000ms). Returns immediately if condition already met.

Passing NEITHER ms nor forElement/selector is an error (HTTP 400), not a no-op success — a wait that reports success without waiting makes every assertion after it race the page.

Response: { success: true, waited: ms, found?: boolean }

Parameters:

Name Type Description
ms number,null Milliseconds to wait
forElement string,null CSS selector to wait for
selector string,null Alias for forElement — accepted so the CLI, the test-runner wait step and this endpoint all take the same field
hidden boolean,null Wait for element to disappear (default false)
timeout number,null Max wait time in ms (default 5000)
pollInterval number,null Polling interval in ms (default 100)
window string,null Target window ID

Examples:


POST /call

Call a method or get a property on an element

Call a method or access a property on an element by selector. Convenience wrapper around /eval.

This avoids writing querySelector boilerplate. Two modes:

Return value is JSON-serialized. Promises are awaited.

Response: { success: true, data: }

Parameters:

Name Type Description
ref string,null Ref ID from /tree output (e.g., 1, 42) - preferred for efficiency
selector string,null CSS selector of the element
method string Method name to call or property name to get (required)
args array,null Arguments to pass (omit to get property value)
window string,null Target window ID

Examples:


POST /navigate

Navigate to a URL

Navigate the browser to a new URL. Waits for page load to complete.

Use /location after to verify navigation succeeded.

Parameters:

Name Type Description
url string URL to navigate to (required)
window string,null Target window ID

Examples:


POST /refresh

Refresh the page

Hard reload the current page, bypassing all caches (CSS, JS, images). Use soft: true for cache-friendly reload.

Parameters:

Name Type Description
soft boolean,null Use cached resources if available (default false = hard refresh that busts all caches)
window string,null Target window ID

Examples:


GET /location

Get current URL and title

Returns current page info.

Response: { url, title, pathname, search, hash }

Use after /navigate to verify you’re on the expected page.


Watch What Happens

POST /events/watch

Start watching semantic events

Begin capturing high-level user actions. Events are aggregated and meaningful:

Presets control verbosity:

Categories: interaction, navigation, input, hover, scroll, mutation, focus, console

Parameters:

Name Type Description
preset string,null Verbosity: minimal, interactive, detailed, debug
categories array,null Specific categories to watch

Examples:


POST /events/unwatch

Stop watching events

Stop capturing semantic events. Events buffer is cleared.


GET /events

Get captured semantic events

Returns buffered events since watch started.

Response: { events: [{ type, timestamp, category, target?, payload }], since, count }

Event types: interaction:click, input:typed, navigation:navigate, hover:dwell, scroll:stop, etc.


GET /events/stats

Get event aggregation statistics

Shows noise reduction metrics.

Response: { rawEvents, semanticEvents, reductionPercent, byCategory: {…} }

Typically see 90%+ reduction (e.g., 2000 raw events → 80 semantic events).


Watch DOM Changes

POST /mutations/watch

Start watching DOM mutations

Begin capturing DOM changes: elements added/removed, attributes changed, text modified.

Presets filter out framework noise:

Get captured mutations via /mutations/status.

Parameters:

Name Type Description
root string,null Root selector to watch (default body)
childList boolean,null Watch child additions/removals (default true)
attributes boolean,null Watch attribute changes (default true)
characterData boolean,null Watch text content changes (default false)
subtree boolean,null Watch all descendants (default true)
debounce number,null Debounce ms (default 100)
preset string,null Filter preset: smart, xinjs, b8rjs, tailwind, react, minimal, none
filters any Custom filter configuration
pierceShadow boolean,null Watch inside shadow DOM (default false)

Examples:


POST /mutations/unwatch

Stop watching mutations

Stop capturing DOM mutations. Call this when done to free resources.


GET /mutations/status

Get mutation watch status

Check if mutation watching is active and get captured mutations.

Response: { watching: boolean, mutations: […], summary: { added, removed, changed } }


User Selection

POST /select

Interactive element selection

Let user point at elements on the page instead of writing selectors.

Actions:

Workflow: start → (user draws region) → result → use returned selectors

Parameters:

Name Type Description
action string,null Selection action to perform (default: result)
window string,null Target window ID

Examples:


POST /select/start

[Deprecated] Use /select with action:”start”

Deprecated: Use POST /select {“action”:”start”} instead.


POST /select/cancel

[Deprecated] Use /select with action:”cancel”

Deprecated: Use POST /select {“action”:”cancel”} instead.


GET /select/status

[Deprecated] Use /select with action:”status”

Deprecated: Use POST /select {“action”:”status”} instead.


GET /select/result

[Deprecated] Use /select with action:”result”

Deprecated: Use POST /select {“action”:”result”} instead.


POST /select/clear

[Deprecated] Use /select with action:”clear”

Deprecated: Use POST /select {“action”:”clear”} instead.


Multiple Tabs

GET /windows

List connected windows

Returns all connected browser windows/tabs with IDs, URLs, and titles.

Response: { windows: [{ id, url, title, focused, active, hidden, windowType }], tabs, count, ready, hint }

The array is windows, not tabs — it includes popups (windowType: "popup") and iframes ("iframe"), not only tabs. Because the CLI command is hj tabs, d['tabs'] is the natural first guess and used to KeyError, so tabs is also sent as an alias of the same array. Prefer windows; tabs exists so the obvious guess works.

Only real tabs can be the target of an UNTARGETED command — a popup or iframe never becomes focused, so a page opening a popup cannot silently re-route your commands.

active and hidden are exact inverses, and BOTH are sent on purpose: /status historically emitted only hidden and /windows only active, so code moving between the two silently inverted its own meaning. Read either; never infer one endpoint’s polarity from the other.

Use window IDs in other endpoints (e.g., /click, /tree) to target specific tabs.

ready is the signal to gate a test lane on, not “is the server up”. A server can be running with zero connected tabs — it answers /status 200 but there is nothing to drive, and a lane that adopts it fails later on a confusing timeout. ready is true when at least one top-level tab is connected. (Hidden tabs count as ready — they’re reachable, just possibly stale; see the hidden-tab warning.) hj doctor checks this and exits non-zero, so a lane can fail fast on the real cause.


POST /tabs/open

Open a new tab

Opens a new tab with optional URL. If url is omitted, opens a blank tab.

In the Haltija desktop app the new tab gets the widget auto-injected, so it’s immediately controllable. Anywhere else (widget injected into a normal browser via bookmarklet/dev-server) there is no tab API, so this falls back to window.open() — the new tab is a plain browser tab with NO widget unless its own page injects one. In that case the response has fallback: true plus a reason, and a top-level warning: the tab will NOT appear in hj tabs and hj commands cannot reach it (they go to the focused widget tab). To control it, inject the widget on that page (e.g. HALTIJA_DEV / haltijaDev) or use the desktop app.

Parameters:

Name Type Description
url string,null URL to open

Examples:


POST /tabs/close

Close a tab

Desktop app only. Closes the specified tab by window ID.

Get window IDs from /windows endpoint.

Parameters:

Name Type Description
window string Window ID to close (required)

Examples:


POST /tabs/focus

Focus a tab (route untargeted commands to it)

Make the given tab the target of untargeted commands. This is a server-side routing change, not a browser action: because it never dispatches to the target tab it cannot time out, even when that tab is hidden.

In the Haltija desktop app it also physically raises the tab, best-effort: the request goes to a tab that is awake, which asks the app’s main process to bring the target forward — so a sleeping tab gets raised without having to run anything itself. Outside the desktop app a background browser tab cannot be raised remotely, so focus is routing-only there. After this, commands without a window/--window go to this tab until you focus another or physically switch tabs in the browser. To pin a single command instead, use --window <id>. If the tab is hidden the response includes a warning (a backgrounded tab’s results can be stale). Returns { success, focused, active, title }.

Parameters:

Name Type Description
window string Window ID to focus (required)

Examples:


Record & Replay

POST /recording

Record user actions and generate tests

Record user interactions and convert them to runnable tests.

Cross-page recording: Recordings now survive page navigations! The server tracks the recording session by window ID, so you can record a multi-page flow (e.g., login → dashboard).

Actions:

Workflow: start → (user interacts, navigates pages) → stop → list → replay

Parameters:

Name Type Description
action string Recording action to perform (required)
name string,null Test name (for generate action)
id any Recording ID or index number (for replay action)
window string,null Target window ID

Examples:


POST /recording/start

[Deprecated] Use /recording with action:”start”

Deprecated: Use POST /recording {“action”:”start”} instead.

Parameters:

Name Type Description
name string,null Recording/test name

POST /recording/stop

[Deprecated] Use /recording with action:”stop”

Deprecated: Use POST /recording {“action”:”stop”} instead.


POST /recording/generate

[Deprecated] Use /recording with action:”generate”

Deprecated: Use POST /recording {“action”:”generate”} instead.

Parameters:

Name Type Description
name string,null Test name
events array,null Semantic events to convert. Omit to use the server-side event buffer (see since).
since number,null When reading from the event buffer, only events after this timestamp
url string,null Starting URL for the generated test (defaults to the first navigation event)
description string,null Test description
tags array,null Tags to attach to the generated test
createdBy string,null Author recorded in the test (‘human’ by default)
addAssertions boolean,null Infer assertions from the events (default true)
suggestAssertions boolean,null Append suggested assertions at the end (default false)
minDelay number,null Drop inter-step delays shorter than this many ms

GET /recordings

[Deprecated] Use /recording with action:”list”

Deprecated: Use POST /recording {“action”:”list”} instead.


Run Tests

POST /test/run

Run a JSON test

Execute a test defined in Haltija JSON format.

Test structure: { “version”: 1, “name”: “Login flow”, “url”: “http://localhost:3000/login”, “steps”: [ { “action”: “type”, “selector”: “#email”, “text”: “user@example.com” }, { “action”: “type”, “selector”: “#password”, “text”: “secret123” }, { “action”: “click”, “selector”: “button[type=submit]” }, { “action”: “assert”, “assertion”: { “type”: “url”, “pattern”: “/dashboard” } } ] }

Step actions: navigate, click, type, check, key, select-text, cut, copy, paste, drag, wait, assert, eval, verify, tabs-open, tabs-close, tabs-focus

Output formats:

Parameters:

Name Type Description
test any Test object with steps (required)
format string,null Output format: json (structured), github (annotations + summary), human (readable)
stepDelay number,null Milliseconds between steps (default 100)
timeout number,null Milliseconds timeout per step (default 5000)
stopOnFailure boolean,null Stop on first failure (default true)
patience number,null Total failures allowed before giving up (0 = use stopOnFailure, default 0)
patienceStreak number,null Consecutive failures to bail immediately (default 2)
timeoutBonusMs number,null Ms added/removed from timeout on success/failure, capped at initial (default 1000)

Examples:


POST /test/suite

Run multiple tests

Execute a suite of tests, optionally stopping on first failure.

Input: { tests: [test1, test2, …], format?, stopOnFailure? }

Response includes per-test results and overall summary.

Parameters:

Name Type Description
tests array Array of test objects (required)
format string,null Output format: json (structured), github (annotations + summary), human (readable)
testDelay number,null Milliseconds between tests (default 500)
stepDelay number,null Milliseconds between steps (default 100)
timeout number,null Milliseconds timeout per step (default 5000)
stopOnFailure boolean,null Stop on first failure (default false for suites)
patience number,null Total failures allowed per test before giving up (0 = use stopOnFailure, default 0)
patienceStreak number,null Consecutive failures to bail immediately (default 2)
timeoutBonusMs number,null Ms added/removed from timeout on success/failure, capped at initial (default 1000)

Examples:


POST /test/validate

Validate test without running

Check that a test is well-formed and all selectors exist on the current page.

Use this to pre-check tests before running. Returns validation errors without executing steps.

Response: { valid: boolean, errors?: [{ step?, message }] }

Parameters:

Name Type Description
test any Test object to validate (required)

Examples:


Escape Hatches

POST /session/attach

Mirror a tmux session into the channel (opt-in, read-only)

Point the session mirror at a running tmux session, so a page — including one on a remote device over a tunnel — can watch the agent work.

Opt-in on purpose. Nothing is mirrored until you attach: the mirror carries EVERYTHING the agent prints, including anything it echoes from a file it just read. There is no default target and no config flag that turns this on.

Writing is a SEPARATE grant. Attaching without allowInput gives a read-only mirror that /session/write will refuse. Typing into an agent’s console can answer a permission prompt — indistinguishable from the operator doing it — so it is never implied by the ability to watch.

Requires a token. This endpoint family refuses to run unless the server was started with --token: it exposes a terminal running an agent with your privileges, and the server binds beyond loopback by default.

Attaching to a name that does not exist FAILS and lists what does, rather than reporting success for a mirror that would show nothing.

Requires the agent to be running inside tmux (tmux new -s agent, then start it there). tmux can attach to a session that is already running, so you do not have to restart your agent.

Parameters:

Name Type Description
target string tmux session name, exactly as tmux list-sessions reports it (required)
allowInput boolean,null ALSO permit typing into the session (default false). A separate grant from reading: typing can answer a permission prompt.

Examples:


POST /session/read

Read the mirrored terminal session

Returns the rendered contents of the mirrored tmux pane as plain text.

Already rendered — capture-pane -p resolves the escape sequences, so this is displayable text rather than a terminal stream. A page can put it in a <pre> without a terminal emulator, which is what makes it usable on a headset.

Returns an error naming the target if the session has gone away, rather than empty text — an empty mirror reads as “the agent is idle”, which would be the wrong conclusion.

Parameters:

Name Type Description
lines number,null How many lines of scrollback to include (default 200, max 10000)

Examples:


POST /session/write

Type into the mirrored session (requires the input grant)

Sends text to the mirrored tmux session exactly as a local user at the keyboard would.

The agent receives it on stdin as a normal turn — there is no queue, no message API and no await primitive, because typing into the agent’s own console is already all three.

Requires the writeKey returned by attach. The handle is minted when allowInput is granted and returned once, to the attaching caller. Without it, reaching this endpoint would equal authority to type into the agent — so a second caller could ride a grant it never requested.

Requires allowInput at attach time. Reading and writing are different risks: reading leaks what the agent prints, writing decides what the agent DOES — including answering a permission prompt, where this is indistinguishable from the operator typing. A mirror attached for watching cannot be typed into.

submit defaults to true (hits Enter), matching /send/message. Pass false to paste without submitting — useful when a human at the real keyboard should read it before it goes in.

Sent as one send-keys call, not per character: two writers on one pty interleave badly, and line-at-a-time is correct for this.

Parameters:

Name Type Description
text string What to type (required)
submit boolean,null Press Enter afterwards (default true)
writeKey string,null The handle returned by /session/attach when allowInput was granted. Required: reaching this endpoint is not the same as holding the capability.

Examples:


POST /session/detach

Stop mirroring the terminal session

Detach the mirror. Reads then report “no session attached” until you attach again.

Examples:


GET /console

Get console output

Returns captured console.log/warn/error/info/debug from the page — AND uncaught exceptions and unhandled promise rejections (as level ‘error’), which never route through console.error. Error objects keep their message and stack (a plain console.error(err) used to serialize to {}).

Response: { entries: [{ level, args, timestamp, stack? }] }

Great for debugging — check for errors after actions fail. Note: capture starts when the widget is injected, so errors thrown BEFORE injection are missed for bookmarklet/dev-server injection; the desktop app injects at document-start and catches them.


POST /eval

Execute JavaScript

Run arbitrary JavaScript in the browser context. Returns the result.

The code runs in the page’s context with access to window, document, etc. Return values are JSON-serialized.

Async code is supported: a returned Promise is awaited, and top-level await works. Multi-statement code needs an explicit return to produce a value.

document.title -> the title await fetch(‘/api’).then(r => r.json()) -> the parsed body const r = await fetch(‘/api’); return r.status -> the status code

Parameters:

Name Type Description
code string JavaScript code to execute (required)
window string,null Target window ID

Examples:


POST /fetch

Fetch a URL from within the tab context

Fetch a URL from within the browser tab’s context. Essential for accessing blob: URLs which are only valid in the tab that created them.

Returns the content as base64 with MIME type. Works with:

Response: { success: true, data: { mimeType, base64, size, url } }

Use this when you see a blob URL in the DOM and need to access its content.

Parameters:

Name Type Description
url string The URL to fetch (blob:, data:, http:, https:) (required)
window string,null Target window ID

Examples:


POST /screenshot

Capture a screenshot

Capture the page or a specific element as PNG/WebP/JPEG.

Works automatically in the Haltija Desktop app. In browser widget mode, captures viewport only.

**canvas — capture a directly (best for 3D / render-to-texture).** Pass a selector and haltija reads the canvas's own pixels via toDataURL instead of capturing the screen. That means: exact pixels at native resolution, **no screen-share grant**, no Electron requirement, and it works even when the canvas is scrolled out of view or the tab isn't frontmost. Ideal for a WebGL scene (Babylon/three.js) or a UI rendered into a texture.

Caveat it handles for you: a WebGL context clears its drawing buffer after compositing unless created with { preserveDrawingBuffer: true }, so a naive toDataURL can silently return a BLANK image. Haltija samples the result and returns a warning explaining that (rather than handing you an empty picture). A canvas tainted by cross-origin content returns a clear error, not a crash.

When file=true (default from CLI), saves to /haltija-screenshots/ and returns the file path. Artifacts older than 24h (and beyond the most recent 200) are pruned automatically. When file=false, returns base64 data URL in response JSON.

displaySurface — what the user ACTUALLY shared. On the source: "getDisplayMedia" path (the 🖥-button grant), the picker only defaults to the current tab; the user can pick anything, and the grant lasts for the session. So a wrong pick means every later capture returns the wrong surface, confidently, until it is re-picked (click 🖥 twice).

displaySurface meaning
"browser" a tab — the expected case, no warning
"window" a WINDOW, not this tab — carries a warning; the pixels are that window
"monitor" a WHOLE MONITOR — carries a warning; may include other apps, won’t follow the page
null this browser does not report it. Not a pass — it means unchecked

If a warning is present, do not reason about layout from that image. Note "browser" means “a tab”, not necessarily your tab: confirming that needs setCaptureHandleConfig(), which mutates document-level state on the page the widget is injected into, and an injected tool should not quietly change its host’s configuration.

The Electron desktop app uses native capturePage instead and needs no grant, so none of this applies there.

Response: { success, path?, image?, width, height, source, displaySurface?, canvas?, warning? }

Parameters:

Name Type Description
ref string,null Ref ID from /tree output - capture specific element
selector string,null Element to capture (omit for full page)
canvas string,null Selector for a — read its pixels directly (WebGL/2D). Exact pixels, no screen-share grant, works off-screen. PIERCES SHADOW DOM: accepts `host >>> canvas`, a plain selector (light DOM then shadow roots), a host element, or an EMPTY string to capture the largest canvas on the page. Best for 3D scenes and render-to-texture UI.
format string,null Image format: png (default), webp, or jpeg
quality number,null Quality for lossy formats (webp/jpeg). Either scale works: 0-1 (canvas-native) or 0-100 (percentage); anything above 1 is read as a percentage and clamped. Ignored for png.
scale number,null Scale factor (default 1)
maxWidth number,null Max width in pixels
maxHeight number,null Max height in pixels
window string,null Target window ID
chyron boolean,null Burn page title, URL, timestamp into image (default true, set false for clean screenshot)
delay number,null Wait ms before capturing (e.g. 1000 to let page settle after navigation)
file boolean,null Save to disk and return file path instead of data URL (default true — pass false for base64)
schematic boolean,null Return a schematic of the page INSTEAD of pixels, even when real capture is available. Cheaper, deterministic, and carries the contrast audit; canvases are still embedded as real pixels.
fallback boolean,null When no pixel capture is available, return a labelled SCHEMATIC of the page instead of failing (default true; canvases are embedded as real pixels since they need no permission). Pass false to hard-fail instead.

Examples:


POST /snapshot

Capture page snapshot

Capture current page state for debugging.

Includes: DOM tree, console logs, viewport size, scroll position, URL, timestamp.

Response: { snapshot: { url, title, viewport, dom, console, timestamp } }

Great for debugging test failures - call this when something goes wrong.

Parameters:

Name Type Description
trigger string,null What triggered the snapshot (e.g., “manual”, “test-failure”)
context any Additional context about the snapshot

Examples:


POST /video/start

Start video recording

Start recording the browser tab as WebM video. Requires the Haltija Desktop app.

The recording saves to /haltija-videos/ when stopped. Max duration is capped to prevent runaway recordings. Recordings older than 24h (and beyond the most recent 20) are pruned automatically — a lower cap than the other artifacts because video files are orders of magnitude larger. Copy anything you want to keep out of that directory.

Response: { success, recordingId }

Parameters:

Name Type Description
maxDuration number,null Max recording duration in seconds (default 60, max 300)
window string,null Target window ID

Examples:


POST /video/stop

Stop video recording

Stop recording and save the video file.

Response: { success, path, duration, size }

Parameters:

Name Type Description
window string,null Target window ID

Examples:


GET /video/status

Check video recording status

Check if video recording is active.

Response: { recording, recordingId?, duration?, window? }

Examples:


Dialogs (alert/confirm/prompt)

POST /dialog/configure

Configure native dialog auto-response policy

Set how native browser dialogs (alert, confirm, prompt) are handled.

By default, Haltija intercepts all native dialogs and auto-responds:

Configure the policy before triggering actions that cause dialogs. Each dialog is logged and reported via the dialog/opened push event.

Response: { policy: { alert, confirm, prompt, beforeunload } }

Parameters:

Name Type Description
alert string,null “dismiss” (only option for alerts)
confirm string,null “accept” or “dismiss”
prompt any “dismiss” or { “response”: “text” } to auto-fill
beforeunload string,null “allow” or “block” — controls page unload
window string,null Target window ID

Examples:


GET /dialog/history

Get recent dialog history

Returns a list of recently intercepted native dialogs.

Each entry includes: type (alert/confirm/prompt), message, response given, timestamp. Buffer holds the last 50 dialogs.

Response: { history: [{ type, message, defaultValue?, response, timestamp }] }


Network Traffic

GET /network

Get captured network requests

Returns buffered network entries in compact format.

Each entry: { m: method, s: status, url: trimmed_url, t: time_ms, sz: human_size, type: resource_type } Status -1 means failed (timeout, CORS, canceled). Summary line always included.

Use preset parameter to override the watch preset for this query.


POST /network/watch

Start capturing network traffic

Begin monitoring HTTP requests and responses via Chrome DevTools Protocol. Requires the Haltija Desktop app (uses Electron’s CDP access).

Presets control what’s captured:

Output is token-optimized: ~10 tokens per request vs 200+ for raw HAR.

Parameters:

Name Type Description
preset string,null Filter preset: errors, minimal, standard, verbose
includePatterns array,null URL regex patterns to always include
excludePatterns array,null URL regex patterns to exclude
maxBuffer number,null Max entries to buffer (default 200)

POST /network/unwatch

Stop capturing network traffic

Stop monitoring network requests. Clears the capture buffer.


GET /network/stats

Network traffic summary

Returns: total requests, failures, pending, average latency, total bytes, and a one-line summary string.