Browser
Browsers: a browser an agent drives, and profiles that keep a person's logins.
profile = noclick.BrowserProfile.from_name("ana-work", create_if_missing=True, user="user_42")
profile.login("https://portal.example.com", reason="to download your invoices").wait()
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.summary)
# closed on exitA browser is Chromium in a sandbox, driven by a model that sees the page and acts on it. NoClick solves the CAPTCHAs it can; when a page still needs a person (a CAPTCHA it couldn't solve, a two-factor code), the browser parks, unbilled, while that person finishes the step on a page NoClick sends them. A profile keeps the sign-ins a person made once; when a site signs it out, the next browser asks that person to sign in again the same way, and saves the new sign-in. A profile counts as a credential, so it is never exported and only its own user's browsers and agents use it.
class BrowserA running browser. with noclick.Browser.create() as browser: closes it on
exit; otherwise it closes at expires_at. Billed per minute while it runs,
plus the model tokens act and extract use; time it waits on a person
is neither billed nor counted against timeout_s.
- Like every live object, it also has hydrate(), refresh() and raw.
- Works as a context manager:
withorasync with.
| id | str | |
| status | str | None |
|
| region | str | None | Where it runs: its profile's region, else the one it was created in. |
| profile_id | str | None | The profile it is signed in with (None if none). |
| agent_id | str | None | The agent driving it, for an agent's browser. |
| live_url | str | None | A NoClick page where you watch it live and can take over; it works while the browser runs. |
| url | str | None | The page it is on. |
| title | str | None | |
| timeout_s | int | None | How long it may run; parked time doesn't count. |
| solve_captchas | bool | None | Whether NoClick solves CAPTCHAs before asking a person. |
| parked_s | int | None | Seconds it has waited parked: not billed, not in timeout_s. |
| cost_usd | float | None | What it has cost so far: minutes and model tokens. |
| user_id | str | None | |
| created_at | Any | |
| expires_at | Any | |
| closed_at | Any |
createclassmethodasync
noclick.Browser.create(
*, profile=None, timeout_s=None, region=None, solve_captchas=True, human=None,
user=..., client=None
) -> BrowserStart a browser that runs at most timeout_s seconds (300 by
default, up to 3600; time it waits on a person doesn't count). With
profile (a BrowserProfile or id) it is signed in to the profile's
sites, acts for the profile's user, runs in the profile's region and saves
new sign-ins into it when it closes: one such browser per profile at a
time, so a second raises noclick.Conflict naming the first
(browser_id). Without one, region is where it runs
(noclick.regions(kind="browser"); the account's default when omitted).
NoClick solves the CAPTCHAs it can before asking a person;
solve_captchas=False turns that off for sites whose terms forbid it.
human is who is asked when a page needs a person (the user by default,
you for an account browser).
NoneNoneNoneTrueNoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The browser, running on a blank page, with its live_url.
Use it as a context manager, so it closes when the block ends:
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")
print(browser.act("Download every invoice from September").summary)Without with, close it yourself or let it run out:
browser = noclick.Browser.create(timeout_s=600)
print(browser.live_url) # watch it, or take over
browser.close()from_idclassmethod
noclick.Browser.from_id(browser_id, *, client=None, user=...) -> BrowserA lazy reference to a browser by id, for one you started earlier.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
A browser that loads the first time you read a field. Nothing is sent yet.
browser = noclick.Browser.from_id("e2c9a4f7-1b3d-4e8a-9c6f-5d0b7a2e1f83")
print(browser.content(format="text"))getclassmethodasync
noclick.Browser.get(browser_id, *, user=..., client=None) -> BrowserLoad a browser by id now; an unknown id raises noclick.NotFound.
Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The browser, loaded: its status, the page it is on and its live_url.
browser = noclick.Browser.get("e2c9a4f7-1b3d-4e8a-9c6f-5d0b7a2e1f83")
print(browser.status, browser.url, browser.live_url)listclassmethodasync
noclick.Browser.list(
*, profile=None, status=None, limit=None, user_filter=None, user=..., client=None
) -> list[Browser]The current user's browsers (the account's own outside as_user, or those
user_filter covers), optionally one profile's; status="live" lists those starting,
running or parked.
NoneNoneReturn at most this many.
None)Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Every matching browser, newest first, fetched page by page. Agents' browsers are listed too.
for browser in noclick.Browser.list(status="live", user_filter=noclick.ALL_USERS):
print(browser.id, browser.url, browser.live_url)
profile = noclick.BrowserProfile.from_name("ana-work", user="user_42")
recent = noclick.Browser.list(profile=profile, limit=10)list_pageclassmethodasync
noclick.Browser.list_page(
*, cursor=None, limit=None, profile=None, status=None, user_filter=None, user=...,
client=None
) -> PageOne page of browsers; pass next_cursor back as cursor.
The next_cursor of the page before; omit it for the first page.
None)Return at most this many.
None)NoneNoneWhich users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A page whose data holds the browsers and whose next_cursor is None on the last page.
page = noclick.Browser.list_page(status="closed", limit=50)
while True:
for browser in page.data:
print(browser.id, browser.cost_usd)
if page.next_cursor is None:
break
page = noclick.Browser.list_page(status="closed", limit=50, cursor=page.next_cursor)gotoasync
browser.goto(url) -> PageStateOpen url and wait for the page to load.
The page it landed on: its url after redirects and its title.
with noclick.Browser.create() as browser:
page = browser.goto("https://example.com")
print(page.url, page.title)actasync
browser.act(
instruction, *, background=False, max_steps=None, files=None, timeout=None
) -> ActResult | OperationHave the model carry out instruction on the page: clicking, typing,
scrolling, uploading, downloading, at most max_steps actions (50 by
default, up to 200). files are https URLs (a bucket's signed_url
works) NoClick downloads into the browser first, for the task to put in a
form, up to 5. NoClick solves the CAPTCHAs it can, giving one half a
minute; one still there, or a two-factor prompt, parks the browser (not
billed, not counted against its timeout) and sends a person a page carrying
the live view, and the step waits until they have dealt with it. So does a sign-in page for a site
whose saved sign-in expired: it asks the person who signed in
(SiteLogin.signed_in_by) to sign in again, saves the new sign-in to the
profile, and carries on.
timeout is how many seconds to wait here; the step keeps going after it.
background=True returns at once.
FalseNoneNoneNoneHow it went: whether it's done, a summary, the steps it took, the url
it ended on and any downloads. With background=True, an Operation at once,
whose wait() gives that result.
Describe the task, not the clicks:
with noclick.Browser.create(profile=noclick.BrowserProfile.from_name("ana-work", user="user_42")) 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"])Run a long task in the background and pick it up later:
browser = noclick.Browser.create(timeout_s=1800)
task = browser.act("Compare the three cheapest flights from SFO to JFK on Oct 12", background=True)
print(task.id) # save it; then, later, anywhere:
result = noclick.Operation.from_id(task.id).wait()Hand it the files a form needs:
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)extractasync
browser.extract(query, *, schema=None) -> AnyRead what query describes off the current page, shaped by schema
(a JSON Schema) when you pass one.
NoneThe data as JSON: an object or a list matching schema, or the model's own shape without 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"}}}},
)
print(invoices[0]["number"], invoices[0]["total_usd"])screenshotasync
browser.screenshot(*, full_page=False) -> bytesA PNG of what the browser shows: the viewport, or the whole page with full_page=True.
FalseThe PNG's bytes.
with noclick.Browser.create() as browser:
browser.goto("https://example.com")
with open("page.png", "wb") as f:
f.write(browser.screenshot(full_page=True))contentasync
browser.content(*, format='markdown') -> strThe current page's content as "markdown", "text" or "html".
'markdown'The page's content in that format; markdown keeps headings, links and tables.
with noclick.Browser.create() as browser:
browser.goto("https://example.com/pricing")
print(browser.content(format="markdown"))closeasync
browser.close() -> NoneClose the browser now (billing stops with it). The profile keeps any new sign-ins.
browser = noclick.Browser.create()
try:
print(browser.goto("https://example.com").title)
finally:
browser.close()BrowserProfile
class BrowserProfileSaved sign-ins for one person. BrowserProfile.from_name("ana-work", create_if_missing=True, user="user_42") is lazy; profile.login(url)
asks the person to sign in once, and browsers started with the profile are signed in.
Its region is where its browsers run; its sign-ins are kept in the US whatever the region.
- Like every live object, it also has hydrate(), refresh() and raw.
| id | str | |
| name | str | |
| region | str | None | Where its browsers run, fixed when it is created. |
| user_id | str | None | |
| last_used_at | Any | When a browser last used it (None if never). |
| created_at | Any | |
| sites | list[SiteLogin] | The sites it is signed in to, most recently signed in first (loads the profile if needed). |
createclassmethodasync
noclick.BrowserProfile.create(
name, *, region=None, user=..., client=None
) -> BrowserProfileCreate an empty profile called name (unique per user; a duplicate
raises noclick.Conflict with existing_id). region is where its
browsers run (noclick.regions(kind="browser"); the account's default
region when omitted), fixed for its life; its sign-ins are kept in the US.
NoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The new profile, signed in nowhere yet.
profile = noclick.BrowserProfile.create("ana-work", user="user_42")
login = profile.login("https://portal.example.com", reason="to download your invoices")from_nameclassmethod
noclick.BrowserProfile.from_name(
name, *, create_if_missing=False, region=None, user=..., client=None
) -> BrowserProfileA lazy reference to the profile called name in the current (or given)
user; with create_if_missing=True it's created, empty, on first use, in
region when you pass one.
FalseNoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A profile that loads, or is created, the first time it's used. Nothing is sent yet.
profile = noclick.BrowserProfile.from_name("ana-work", create_if_missing=True, user="user_42")
if not any(site.domain == "portal.example.com" for site in profile.sites):
profile.login("https://portal.example.com", reason="to download your invoices").wait()from_idclassmethod
noclick.BrowserProfile.from_id(profile_id, *, client=None, user=...) -> BrowserProfileA lazy reference to a profile by id.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
A profile that loads the first time you read a field. Nothing is sent yet.
profile = noclick.BrowserProfile.from_id("b7e1d3a9-4c2f-4e6b-8a5d-1f9c0e3b7a24")
print([site.domain for site in profile.sites])getclassmethodasync
noclick.BrowserProfile.get(profile_id, *, user=..., client=None) -> BrowserProfileLoad a profile by id now; an unknown id raises noclick.NotFound.
Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The profile, loaded, with the sites it is signed in to.
profile = noclick.BrowserProfile.get("b7e1d3a9-4c2f-4e6b-8a5d-1f9c0e3b7a24")
for site in profile.sites:
print(site.domain, site.signed_in_at, site.last_used_at)listclassmethodasync
noclick.BrowserProfile.list(
*, limit=None, user_filter=None, user=..., client=None
) -> list[BrowserProfile]The current user's profiles (the account's own outside as_user, or those user_filter covers).
Return at most this many.
None)Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Every matching profile, newest first, fetched page by page, each with its sites.
for profile in noclick.BrowserProfile.list(user_filter=noclick.ALL_USERS):
print(profile.user_id, profile.name, [site.domain for site in profile.sites])list_pageclassmethodasync
noclick.BrowserProfile.list_page(
*, cursor=None, limit=None, user_filter=None, user=..., client=None
) -> PageOne page of profiles; pass next_cursor back as cursor.
The next_cursor of the page before; omit it for the first page.
None)Return at most this many.
None)Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A page whose data holds the profiles and whose next_cursor is None on the last page.
page = noclick.BrowserProfile.list_page(limit=50)
while True:
for profile in page.data:
print(profile.name, profile.last_used_at)
if page.next_cursor is None:
break
page = noclick.BrowserProfile.list_page(limit=50, cursor=page.next_cursor)loginasync
profile.login(url, *, human=None, reason=None) -> BrowserLoginAsk a person to sign in to the site at url in a live browser; the
sign-in is saved to this profile. human is who is asked (a
noclick.Human): the profile's user when omitted. An account profile
with no human sends nothing, so pass the link's url on yourself.
You ask once: when the site signs the profile out later, the browser that
meets its sign-in page asks the same person again (SiteLogin.signed_in_by)
and saves the new sign-in. The link's browser starts (and is billed) only
once the person opens it, so a link nobody opens costs nothing.
NoneNoneThe sign-in link, already sent: its url, when it expires_at (in
24 hours) and where it went. wait() returns the SiteLogin once the person has signed in.
Ask the user once, then use the sign-in whenever you need it:
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")
site = login.wait()
print(site.domain, site.signed_in_at)Or, for your account's own profile, ask someone by email:
profile = noclick.BrowserProfile.from_name("acme-ops", create_if_missing=True)
login = profile.login("https://vendor.example.com", human=noclick.Human(email="ops@acme.test"))
print(login.delivered_via) # ['email']forgetasync
profile.forget(domain) -> NoneSign the profile out of domain and drop what it and its subdomains
stored. A short browser (about a minute, billed like any) clears them and
saves the profile; this returns once it has. It refuses
(noclick.Conflict naming the browser) while another browser saves into
the profile, and a browser already using the profile keeps the old state
until it closes.
profile = noclick.BrowserProfile.from_name("ana-work", user="user_42")
profile.forget("portal.example.com")deleteasync
profile.delete() -> NoneDelete the profile and every sign-in in it.
noclick.BrowserProfile.from_name("ana-work", user="user_42").delete()BrowserLogin
class BrowserLogin(Operation)A live-browser link where a person signs in to a site, and the operation
that finishes once they have: wait() returns the SiteLogin.
- Inherits from Operation.
| url | str | The page where the person signs in, in a live browser. |
| expires_at | Any | |
| domain | str | None | The site's domain. |
| delivered_via | list[str] | Channels the link went out on ( |
| undelivered | list[dict[str, Any]] | Channels that failed, each with a |
listclassmethodasync
noclick.BrowserLogin.list(*, limit=None, **filters) -> list[Operation]Sign-in operations (kind browser.login), newest first; takes
Operation.list's filters except kind.
Return at most this many.
None)Every matching operation (or the first limit); wait() on one returns the SiteLogin.
for op in noclick.BrowserLogin.list(status="awaiting_human", user_filter=noclick.ALL_USERS):
print(op.user_id, op.summary_text, op.created_at) # ... Sign in to portal.example.com ...list_pageclassmethodasync
noclick.BrowserLogin.list_page(*, cursor=None, limit=None, **filters) -> PageOne page of sign-in operations; pass next_cursor back as cursor.
The next_cursor of the page before; omit it for the first page.
None)Return at most this many.
None)A page whose data holds the operations and whose next_cursor is None on the last page.
page = noclick.BrowserLogin.list_page(status="expired", limit=50)
while True:
for op in page.data:
print(op.summary_text, op.updated_at)
if page.next_cursor is None:
break
page = noclick.BrowserLogin.list_page(status="expired", limit=50, cursor=page.next_cursor)summaryclassmethodasync
noclick.BrowserLogin.summary(**filters) -> OperationSummaryCount sign-in operations; takes Operation.summary's filters except kind.
The total that match and a count for every status in by_status.
counts = noclick.BrowserLogin.summary(user_filter=noclick.ALL_USERS)
print(counts.by_status["awaiting_human"], counts.by_status["succeeded"])PageState
class PageStateWhere the browser is: the page's url (after redirects) and title.
| url | str | None | |
| title | str | None | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
ActResult
class ActResultHow an act went: done says whether the task was finished (False:
it ran out of steps or couldn't go on, and summary says why). steps are
the actions it took (action, detail, url); downloads the files it
saved (name, size_bytes, and a signed url valid for an hour).
| done | bool | |
| summary | str | |
| steps | list[dict[str, Any]] | |
| url | str | None | |
| downloads | list[dict[str, Any]] | |
| cost_usd | float | None | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
SiteLogin
class SiteLoginA site a profile is signed in to: its domain, when the person signed in and
who did, and when a browser last used the sign-in (None if never). When the site
signs the profile out, the next browser that meets its sign-in page asks
signed_in_by to sign in again in the live view, as it asks for a two-factor
code, and saves the new sign-in; signed_in_at then moves.
| domain | str | |
| signed_in_at | Any | |
| signed_in_by | str | None | The email of the person who signed in (the one |
| last_used_at | Any | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |