Docs
API 1.39
Status
Concepts

The browser tier

What Kaabist does with a real browser: isolation, asset blocking, real clicks, waiting for a one-time code, what a page can see, and the limits.

When a program has to click, type or wait for JavaScript, its job runs at the browser tier: a real Chromium, one fresh context per job, driven over the DevTools protocol. It costs more than the http tier, so Kaabist blocks what a program never reads and keeps each job isolated from every other.

#When to use it

Kaabist moves a job here by itself when the program needs it (see Tiers and escalation). Ask for it with options.prefer_tier: "browser" only when you already know a site renders its content in JavaScript, to skip the failed http attempt. The stealth tier is the same browser, hardened and on residential addresses, for sites that refuse ordinary traffic; it needs a paid plan.

The stealth tier has a second browser for sites that refuse even that one: Camoufox, a Firefox build. A stealth job starts on Chromium. If a site refuses it, the job tries once more on Camoufox, and later jobs on that site start there. Set options.engine to "firefox" to start on Camoufox, or to "chromium" to never switch. Three things change on Camoufox:

  • No live view and no takeover. A job parked for a person is continued in a Chromium browser of its own.
  • Sessions stay on their engine. A session saved on one engine is refused on the other with session_engine_mismatch, because a site that ties its sign-in to the browser would refuse it anyway.
  • Compute costs the same. Camoufox is a little slower per page, so a long job can cross the next ten-second boundary sooner.

#Your job gets its own context, and sometimes its own process

Every job runs in a fresh browser context: its own cookies, storage and cache, nothing inherited from the job before it and nothing left for the job after. Contexts of different customers share one Chromium process, except when your job carries a session or an input of type: "secret"; then it runs in a process reserved for your organisation. A context separates data; a process separates what a browser bug could reach.

#Images and trackers are not loaded

Images, video and fonts are never fetched, and neither is anything from a curated list of analytics, advertising and consent-banner domains. Proxy bandwidth is most of what a browser job costs, and on a typical page those requests are most of the bandwidth. meta.assets reports what was refused:

"assets": { "blocked_requests": 34, "blocked_bytes_estimate": 1530000 }

blocked_bytes_estimate is an estimate: a refused request has no response to measure, so it is the count per resource type times that type's typical size.

Nothing a program reads is affected: the document, XHR and fetch responses, scripts and stylesheets always load. To block more (a site's own analytics, which the shared list cannot name), list glob patterns in options.block_urls. To load images, video and fonts, for a screenshot a person will look at or a layout that depends on an image, set options.assets: "allow". The analytics, advertising and consent-banner domains stay refused either way: they show nothing a program reads, and a page keeps talking to them for seconds after it has loaded. A live takeover or editor refuses them too, and loads the images, since a person is looking. A page that loads its own code at runtime and times out while requests were being refused fails with a hint to set assets: "allow".

#Clicks are clicks

A click is a mouse: the element is scrolled into view, the middle of its box is found, and a pointer moves there, presses and releases. It is not element.click(), which no :hover rule believes and which "clicks" a button behind a cookie banner.

Before acting on an element, Kaabist waits for it to be there, visible, enabled and not covered. It asks the page what is painted at the point it is about to press; if the answer is a dialog rather than your button, the obstacle layer deals with the dialog, and if it cannot, the step reports covered_target and names what was in the way instead of clicking something you did not ask for.

fill replaces a field's contents in one edit, the way a paste does. A field with an input mask or a per-keystroke validator may need the keys themselves: set options.fill_mode: "type". A page that opens a link in a popup (window.open, target=_blank) can be followed in the same tab with options.follow_popups: true.

#Waiting for a code from your phone

When a program reaches await_input (a one-time password, an e-mail code), the job parks and the browser stays open on the machine that parked it, for the step's ttl: five minutes by default, never longer than fifteen. Answer inside that window with POST /v1/jobs/{id}/input and the run carries on in the page it was on, still signed in and on the same step.

Answering later loses nothing. When the window ends, the session is saved to the job's checkpoint and the browser is released; the resume reopens the page and restores the session. It is a few seconds slower. See Human in the loop.

#What the page can see

A Kaabist job leaves no trace in the page it visits. Locators are resolved in an isolated JavaScript world: your target expressions run against the real DOM, but nothing is written to it, no globals appear in the page's own world, and a MutationObserver on the page records nothing from Kaabist finding an element. evaluate is the exception you opt into: it runs your script in the page's own world.

The browser presents a consistent identity: the user agent, client hints, viewport, locale and time zone agree with each other and with the proxy the traffic comes from. options.browser_profile: "mobile" swaps the whole set, not only the user-agent string.

#What it costs, and what stops it

Compute is 5 credits for a browser job (20 at stealth), plus 1 for every started ten seconds beyond the first thirty, and proxy traffic is billed per megabyte. Time a job spends parked is not billed.

LimitDefaultOption
Whole job300 s (30 min for a program with await_input), parked time excludedmax_duration_ms
Steps200max_steps
evaluate5 s, 1 MB resultper step
download64 MBdownload.max_bytes
Viewport1280 × 800viewport

A download is the only way a job touches a file. It is streamed and cut off at the cap rather than measured afterwards, and it is stored as one of the job's artifacts. Screenshots are taken on failure by default (policies.screenshot: "on_failure"); set always to keep one per step.

#Worked example

Run the single-page app at https://app.example.com, whose script fetches its product list as JSON after the page loads:

{
  "kaab": "1",
  "name": "app-products",
  "options": { "prefer_tier": "browser" },
  "steps": [
    { "goto": { "url": "https://app.example.com/", "wait_until": "networkidle" },
      "expect": { "count": { "target": "#products .product-card", "min": 1 } } },
    { "extract": { "products": { "selector": "#products .product-card", "many": true,
        "fields": { "title": ".title", "sku": { "selector": ".sku", "regex": "SKU:\\s*([A-Z0-9-]+)" } } } } }
  ]
}

The network log. The job's snapshot (GET /v1/jobs/{id}/snapshot) has tier: "browser" and a network list: the document, the script bundle, the GET of the product JSON with its body kept, and the request the page sends to its own /collect beacon. JSON bodies up to 512 KB are stored; other bodies are not. That stored JSON is what API discovery compares the extracted values against.

What blocking saved. meta.assets counts the requests the browser refused and estimates their bytes. Run the job again with "assets": "allow" in options: meta.assets is absent, data is identical, and the proxy line of meta.credits.breakdown is larger.

A failed step. Add a step that clicks something the page does not have:

{ "click": "#not-on-this-page", "timeout_ms": 1000 }

The target never appears on the step's own page, so the job parks as needs_attention with reason unknown_page. attention.detail names the selector (target_missing: #not-on-this-page, followed by an explanation), attention.screenshot links to the page as it was when the step gave up, and attention.snapshot to its HTML. GET /v1/jobs/{id}/steps shows the same screenshot on the failed step's row.

#Pitfalls

  • A browser for a document. prefer_tier: "browser" on a server-rendered page multiplies the compute cost by five for the same data.
  • Allowing assets for good. assets: "allow" is for pages that need it; it can multiply proxy traffic.
  • Fixed sleeps. A sleep is billed browser time and still races the page. Use expect or wait_for with a condition.
  • evaluate for everything. It runs in the page's own world, is invisible to the healer and needs a verified payment method; prefer locators and extract.

#Reference

Was this page helpful?