Browsers

Give an agent a browser, and keep a person's logins in a profile it can use.

A browser is Chromium running in a sandbox, driven by a model that looks at the page and acts on it. It's for the sites with no API: a supplier portal, a government form, an airline. A browser profile keeps a person's sign-ins, so they sign in once and your code and agents use the site as them from then on.

Start a browser

import noclick

with noclick.Browser.create(timeout_s=600) as browser:
    page = browser.goto("https://example.com/pricing")
    print(page.title)
    print(browser.content(format="markdown"))

Leaving the with block closes the browser. Without it, call browser.close(), or let it close at expires_at.

OptionDefaultWhat it does
profilenoneA BrowserProfile (or its id): the browser is signed in to its sites.
timeout_s300How long it may run, up to 3,600 seconds. Time it waits on a person doesn't count.
regionyour account's defaultWhere it runs: one of the regions browsers run in. A browser with a profile runs in the profile's region.
solve_captchasTrueNoClick solves the CAPTCHAs it can before asking a person. Turn it off for sites whose terms forbid it.
humanthe browser's userWho is asked when a page needs a person (see below).

Tell it what to do

act takes a task in plain words and carries it out: clicking, typing, scrolling, choosing from menus, uploading, downloading.

profile = noclick.BrowserProfile.from_name("ana-work", user="user_42")
with noclick.Browser.create(profile=profile, timeout_s=900) as browser:
    browser.goto("https://portal.example.com/invoices")
    result = browser.act("Download every invoice from September")
    print(result.done, result.summary)
    for file in result.downloads:
        print(file["name"], file["url"])   # a signed link, valid for an hour

The result says whether it's done, sums up what happened, lists the steps it took and the url it ended on, and links any downloads. When it can't finish (the page doesn't have what you asked for, or it ran out of steps) done is False and summary says why. An act takes at most 50 actions unless you pass max_steps, up to 200.

A form that needs a file gets it through files: up to 5 https URLs, which NoClick downloads into the browser before the task starts. A bucket's signed_url works:

w9 = noclick.Bucket.from_name("forms").signed_url("w9-2026.pdf")
with noclick.Browser.create(profile=noclick.BrowserProfile.from_name("ana-work", user="user_42")) as browser:
    browser.goto("https://supplier.example.com/onboarding")
    print(browser.act("Fill in the onboarding form and attach the W-9", files=[w9]).summary)

act waits for the task to finish. For a long task, background=True returns an operation straight away:

browser = noclick.Browser.create(timeout_s=1800)
task = browser.act("Compare the three cheapest flights from SFO to JFK on Oct 12", background=True)
result = task.wait()

Read a page

extract reads what you describe off the current page, shaped by a JSON Schema when you pass one:

with noclick.Browser.create() as browser:
    browser.goto("https://portal.example.com/invoices")
    invoices = browser.extract(
        "the invoice numbers and totals",
        schema={"type": "array", "items": {"type": "object", "properties": {
            "number": {"type": "string"}, "total_usd": {"type": "number"}}}},
    )
    png = browser.screenshot(full_page=True)
    text = browser.content(format="text")

content gives the page as "markdown" (the default, which keeps headings, links and tables), "text" or "html". screenshot gives a PNG of the viewport, or of the whole page with full_page=True.

Watch it, or take over

browser.live_url is a NoClick page showing the browser live. Open it to watch what the model does, or take over the mouse and keyboard; the model waits while you do. It works on a phone too: tap to click, swipe to scroll, and a box under the view types into the page. It works only while the browser runs and needs no NoClick sign-in, so treat it like a password. When the browser closes, every view open on it stops at once.

When a page needs a person

NoClick solves the common CAPTCHAs itself, and gives a CAPTCHA half a minute to be solved before anyone is asked. A step the model can't do (a CAPTCHA that's still there, a two-factor code, a "confirm it's you" email) parks the browser: its status is parked, it isn't billed, and the wait doesn't count against timeout_s. NoClick sends a person a page with the live view and a Done button, and the act waits (its operation is awaiting_human) until they've dealt with it and tapped Done; then the task carries on where it stopped. The browser keeps its page while it waits, for up to 24 hours.

The ask goes to the user the browser runs for, or to your account's owner when it runs for your account. Pass human= to ask someone else:

browser = noclick.Browser.create(timeout_s=900, human=noclick.Human(email="ops@acme.test"))

Pay at a checkout

An agent holding a card pays at checkouts in its browser. An act that reaches a payment step with no purchase ready stops there, not done, and its result's payment_due says what the checkout charges; the agent announces that purchase (card__purchase), a person approving it first when it's above the card's threshold, and acts again. That act pays: NoClick fills in the number and CVC of a number for exactly that purchase from the card issuer's own element, through the browser, so they never pass through NoClick's servers or the model (see Pay at a checkout). Card number and CVC fields are masked in every screenshot and every page's content a model reads, and in what screenshot and content return. A checkout that asks for a one-time code (3-D Secure) gets the one the card issuer sends the card, typed in by NoClick.

Keep a person's sign-ins in a profile

A profile holds one person's sign-ins. You create it, then ask the person to sign in to a site once, in a live browser; the sign-in is saved to the profile:

profile = noclick.BrowserProfile.from_name("ana-work", create_if_missing=True, user="user_42")
login = profile.login("https://portal.example.com", reason="to download your invoices")
print(login.url)   # the page the person signs in on

site = login.wait()
print(site.domain, site.signed_in_at)

login sends the person a link to a page with a live browser, open at url, with your reason beside it. It goes to the profile's user unless you pass human= (your account's own profile sends nothing, so pass login.url on yourself). The browser starts when the person opens the link, so a link nobody opens costs nothing; when they've signed in, they tap Done and the sign-in is saved. The link lasts 24 hours. login returns an operation that is awaiting_human until they've signed in, then gives the SiteLogin.

A profile is ready for any site it lists:

profile = noclick.BrowserProfile.from_name("ana-work", user="user_42")
for site in profile.sites:
    print(site.domain, site.signed_in_at, site.last_used_at)

profile.forget("portal.example.com")   # signs out and drops what the site stored

forget isn't instant: a short browser (about a minute, billed like any other) clears what the site and its subdomains stored and saves the profile, and forget returns once it has. A browser already using the profile keeps the old state until it closes.

When a sign-in expires

Sites sign people out now and then: after a few weeks, a password change, a new-device check. You don't ask again: a browser with the profile that lands on the sign-in page of a site the profile was signed in to handles it like a two-factor code. It asks the person who signed in to do it again on a page with the live view, and the step waits (awaiting_human) until they have. The new sign-in is saved to the profile when the browser closes, its signed_in_at moves, and the task carries on. An agent's browser does the same, its turn waiting as it would for a two-factor code, so a scheduled job needs no code for it.

profile = noclick.BrowserProfile.from_name("ana-work", user="user_42")
for site in profile.sites:
    print(site.domain, "signed in by", site.signed_in_by, site.signed_in_at)

signed_in_by is who is asked: the email of the person login asked, the profile's user or the human you passed. A sign-in made through a link you passed on yourself has none, and the browser's own human is asked instead. The ask lasts 24 hours, like a login link; if nobody signs in by then, the step fails with the site still signed out.

Use a profile

Start a browser with the profile and it's signed in to the profile's sites. A browser with a profile acts for the profile's user, since a profile is only ever used by its own user, and runs in the profile's region:

profile = noclick.BrowserProfile.from_name("ana-work", user="user_42")
with noclick.Browser.create(profile=profile) as browser:
    browser.goto("https://portal.example.com/account")
    print(browser.extract("the account holder's name and plan"))

New sign-ins a browser makes, and cookies a site refreshes, are saved back to the profile when the browser closes. So one browser at a time uses a profile: two would each save their own copy and lose the other's sign-ins. Starting a second raises noclick.Conflict naming the first (browser_id); close it, or wait for it to finish.

A profile's browsers reach sites from the same address every time, so a site sees the person come back rather than a new device.

Give an agent a browser

Put "browser" in an agent's tools, and pass browser_profile to sign its browser in with a profile of the agent's own user (your account's, for an account agent):

import noclick

with noclick.as_user("user_42"):
    profile = noclick.BrowserProfile.from_name("ana-work")
    fetcher = noclick.Agent.from_name(
        "invoice-fetcher",
        create_if_missing=True,
        instructions="Download last month's invoices from the vendor portal and store them in the bucket.",
        tools=["browser", noclick.Bucket.from_name("invoices").only("put", "signed_url")],
        browser_profile=profile,
    )

The model gets four tools: browser__goto (open a page), browser__act (a task in plain words, as act does it, with files and max_steps), browser__extract (data off the page, shaped by a JSON Schema) and browser__content (the page as Markdown or text). "browser.goto" and the like give it some of them. Its calls follow your approval rules as browser.goto, browser.act, browser.extract and browser.content (browser.*); a test run gets no act.

Each conversation has a browser of its own. The first browser call starts it, in the profile's region (else the agent's). When a turn ends the browser waits for the conversation's next one, on the same page, for up to 10 minutes: it's parked, so the wait isn't billed and doesn't count against its timeout. Then it closes, saving new sign-ins to the profile, and the next turn starts a fresh one, signed in the same way. A waiting browser also closes, saving, when a new browser needs its place: one more than your account's 5, or one that saves into the same profile. When another browser is already saving into the profile, the conversation's browser uses the profile's sign-ins without saving its own. A file act downloads comes back as a signed link, which a bucket's put stores from url.

browser__act waits up to 50 seconds. A task that needs a person (a sign-in, a CAPTCHA it couldn't solve, a code) asks them as act does, and the turn waits awaiting_human: once they've finished and the task is done, what came of it is the conversation's next turn, and message() returns the reply to that. A task still running after 50 seconds wakes the conversation when it's done. The browser stays open for that task, then closes when the turn it wakes ends. An agent has its browser in its own conversations, not in a channel's.

How profiles are kept

A profile is a Chromium user-data directory, kept encrypted by the browser's provider, in the US whatever the profile's region (its region is where its browsers run). It counts as a credential, like a connection:

  • Its cookies and stored passwords never come back through the API, and it can't be exported: a user's export lists their profiles and the sites each is signed in to, never the sign-ins.
  • Only its own user's browsers and agents use it; another user gets noclick.NotFound.
  • NoClick keeps its own encrypted copy of each profile, updated daily, so a sign-in survives a problem at the provider. It's never handed out.
  • Deleting the user deletes their profiles. profile.delete() deletes one, closing its browsers first, and NoClick's copy with it.

Choose the region when you create a profile (region="us-east"); it can't change after, and its browsers run there (see where browsers run).

Where browsers run, and their network

noclick.regions(kind="browser") lists the regions browsers run in now. On NoClick's hosted platform that's us-east alone for the moment. Asking for another, with region, a profile kept there, an agent's region or your account's default, raises noclick.NotAvailable naming the regions that work, and nothing starts or is charged. A deployment check runs its browser wherever browsers run, whatever your default.

For the moment, too, a browser reaches sites through its provider's shared network rather than an address of its own: its address changes from one browser to the next, even between browsers of the same profile. A profile's sign-ins still carry over, but more sites refuse or challenge shared networks than a fixed address, so a site may ask a person to confirm a sign-in again, or refuse the browser (below). Where a deployment gives each profile an address of its own, every browser of the profile comes from it, in its region's country.

Sites that refuse a browser

A browser reaches sites through NoClick's network. Some sites, mostly banks, payment processors and government services, refuse traffic from networks like it. Whether a browser reaches them directly instead depends on the deployment. Where it does, it reaches them directly, and any other site found refusing the browser is reached directly from then on, its page opened again. Where it doesn't, such a site may refuse the browser, and you're told which hosts did. goto raises:

try:
    browser.goto("https://online.examplebank.com/statements")
except noclick.InvalidRequest as err:
    print(err.refused_hosts)   # ['online.examplebank.com']

and an act names them in a network step of its steps (a payment form served from one of them won't load). A person can do that step in their own browser.

Limits and billing

  • A browser is billed per minute while it runs, its network traffic included, plus the model tokens act and extract use. Time it's parked, waiting on a person or for an agent's next turn, isn't billed. browser.cost_usd says what it has cost so far, and it counts toward the spend cap of the user it runs for.
  • An account runs up to 5 browsers at a time, parked ones included; more raises noclick.QuotaExceeded.
  • NoClick runs a set number of browsers at a time across its platform. When they're all in use, starting one raises noclick.RateLimited with retry_after_s, which the SDK waits out and retries for you; a browser frees up as soon as another one closes, so close yours when you're done.
  • A user has up to 20 profiles.
  • Browsers are for acting for a person on sites they use. Don't use them for bulk scraping of sites whose terms forbid it; such use is suspended.
  • Starting and driving browsers needs the key's browsers scope.

Errors

ErrorWhen
noclick.NotFoundNo browser or profile by that id or name for this user.
noclick.NotAvailableThe browser has closed or run out of time, or browsers don't run in the region asked for right now (regions names where they do).
noclick.QuotaExceeded5 browsers are already running, or the user has 20 profiles.
noclick.RateLimitedBrowsers are at capacity right now: retried for you, then raised with retry_after_s.
noclick.ConflictA profile of the same user already has that name (existing_id names it), another browser is saving into the profile (browser_id names it; an agent's browser waiting for its next turn closes instead), or the browser is parked waiting on a person.
noclick.InvalidRequestA region browsers never run in (us-west), a profile's browser asked for another region, or a page goto couldn't open (its site down, or refusing the browser: refused_hosts names it).
noclick.OperationExpiredNobody signed in within 24 hours of a login, or finished a step within 24 hours.

Every method is in the Browser reference: Browser.act, BrowserProfile.login and the rest.