Metadata-Version: 2.4
Name: runnerid
Version: 0.1.0
Summary: Ask runnerID to fill a browser from a Browser Use agent.
Author: Torchrunner, Inc.
License-Expression: MIT
Project-URL: Homepage, https://runnerid.torchrunner.ai
Project-URL: Documentation, https://api.runnerid.torchrunner.ai/llms.txt
Keywords: runnerid,ai-agents,browser-use,login,2fa,totp
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx

# runnerid

```sh
pip install runnerid
```

Small Python client for the runnerID REST API. Use it from a [Browser Use](https://github.com/browser-use/browser-use) agent that is already attached to Chrome over CDP.

The calls match the Stagehand helper: `login`, `code`, `email_code`, `restore`, `save`, and `approve`. A public CDP URL is sent to runnerID, and the server types the secret into the page. The return value does not include the secret. A laptop browser uses the local route below.

Plans start at $12 a month: https://runnerid.torchrunner.ai/#pricing. After you pay, an emailed sign-up link shows your API key (`rid_...`) once.

Use the API address (yours may differ): `https://api.runnerid.torchrunner.ai`. Set `RUNNERID_BASE_URL` to that address.

`httpx` is imported when you make a call, so `import runnerid` works before it is installed. `pip install runnerid` pulls `httpx` in. From a clone of this repo, `pip install -e packages/browser-use` works too.

## Inside a Browser Use agent

Browser Use is driving Chrome at a CDP address. Pass that same address to `Runner`, then call `login` before or during the task.

```python
import asyncio
import os
from browser_use import Agent, Browser
from playwright.async_api import async_playwright
from runnerid import Runner

CDP = "http://127.0.0.1:9222"

async def main() -> None:
    browser = Browser(cdp_url=CDP)
    # Playwright page for the same Chrome. The local route types into it.
    playwright = await async_playwright().start()
    chrome = await playwright.chromium.connect_over_cdp(CDP)
    page = chrome.contexts[0].pages[0]
    rid = Runner(
        api_key="rid_...",
        identity_id="IDENTITY_ID",
        base_url=os.environ["RUNNERID_BASE_URL"],
        cdp_url=CDP,
        page=page,
    )
    await rid.login("example.com")
    agent = Agent(
        task="Finish signing in on example.com. The password is already typed.",
        browser=browser,
    )
    await agent.run()

asyncio.run(main())
```

## On your own laptop

When `cdp_url` is loopback, private, link-local, or a unix socket, the hosted service cannot connect. Pass the Playwright `Page` Browser Use exposes as `page`. The helper redeems a one-time token (`POST /v1/identities/:id/secret-token`, then `POST /v1/redeem`) and types with `page.fill`. An email code is read from the inbox and typed the same way.

It checks the host again immediately before typing. The host must still be the site (a leading `www.` is ignored). If the host changed, it raises `page_navigated` and types nothing.

This local route is weaker than server fill. The secret passes through your code once. The return value does not include it, and it is not logged. Prefer server fill when the browser is on a network the server can connect to.

A public CDP address still uses server fill. If the server answers that the address is not allowed, and a page was passed, the helper uses the local route. `save` and `restore` still need a CDP address the server can reach.
