> ## Documentation Index
> Fetch the complete documentation index at: https://docs.surfsky.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> Install the Python SDK, automate a page, run concurrent jobs, and reuse a persistent profile.

Requires Python 3.12 or newer. Install the published `surfsky` package:

```bash theme={null}
pip install surfsky
```

Set `SURFSKY_API_TOKEN` and `SURFSKY_API_BASE_URL` from the [dashboard](https://app.surfsky.io). See [credential setup](/quickstart#before-you-start). You can also pass `api_token` and `base_url` to the client constructor.

## Start a browser

Save as `first_browser.py` and run `python first_browser.py`:

```python theme={null}
import asyncio
from surfsky import AsyncSurfsky


async def main():
    async with AsyncSurfsky() as client:
        async with client.browser() as browser:
            await browser.goto("https://example.com")
            print(await browser.title())


asyncio.run(main())
```

Expected output is `Example Domain`. The browser context manager stops the session on exit, including when a page operation raises an exception. Cleanup still requires a running process and a working API connection.

`AsyncSurfsky` provides browser automation and asynchronous REST calls. The synchronous `Surfsky` client provides REST calls and managed sessions for use with other browser frameworks.

<span id="driving-the-page" />

## Interact with the page

Page operations run inside the `client.browser()` block. Common methods include `goto`, `click`, `type`, `hover`, `inner_text`, and `wait_for_selector`. Wait timeouts are in seconds and default to 30; expiration raises `BrowserTimeoutError`.

Use selectors from your target page. For example, in a form with an email field:

```python theme={null}
await browser.type('input[name="email"]', "reader@example.com")
await browser.click('button[type="submit"]')
await browser.wait_for_selector(".confirmation", timeout=15)
print(await browser.inner_text(".confirmation"))
```

`evaluate()` executes JavaScript. It uses an isolated execution context by default; pass `isolated=False` when the script needs variables defined by the page. The DOM is shared, so page code can observe DOM changes made by your script.

```python theme={null}
print(await browser.evaluate("document.title"))
```

### Save traffic

Pass `block_resources` when starting the browser:

```python theme={null}
async with client.browser(block_resources={"image", "font", "media"}) as browser:
    await browser.goto("https://example.com")
```

Keep resources needed for screenshots, layout, or challenges. See [Speed optimization](/speed-optimization).

<span id="read-the-api-instead-of-the-html" />

### Capture a page's API response

Start capture before the request occurs. Adapt the URL and matching path to your application:

```python theme={null}
await browser.capture_responses("/api/search")
await browser.goto("https://your-site.example/search")
response = await browser.wait_for_response("/api/search")
data = response.json()
```

### Multiple tabs

`browser` operates on the first tab. `browser.pages` includes other tabs and popups:

```python theme={null}
await browser.new_page()
await browser.pages[1].goto("https://example.com")
print(await browser.pages[1].title())
```

Close tabs you no longer need. See the [session page limit](/troubleshooting#session-page-limit).

<span id="process-items-concurrently" />

## Running many browsers

This example runs two jobs concurrently and reports each outcome:

```python theme={null}
import asyncio
from surfsky import AsyncSurfsky


async def title(browser, url):
    await browser.goto(url)
    return await browser.title()


async def main():
    async with AsyncSurfsky() as client:
        outcomes = await client.map(
            title,
            ["https://example.com", "https://example.org"],
            concurrency=2,
        )
        for outcome in outcomes:
            print(outcome.value if outcome.ok else outcome.error)


asyncio.run(main())
```

The pool can reuse browsers between jobs. A leased browser retains its fingerprint, proxy, and cookies between leases. Use `browser.retire()` to replace it after the current lease when a job needs a fresh identity.

For manual leasing:

```python theme={null}
async with client.browsers(concurrency=2) as pool:
    async with pool.lease() as browser:
        await browser.goto("https://example.com")
```

Account limits include browsers started by other workers. See [Concurrency](/concurrency) before distributing work across processes.

## Proxies

Pass a proxy choice to `browser()` or `session()`:

```python theme={null}
from surfsky import PremiumProxy, SharedProxy

proxy = PremiumProxy(country="us")
# Other choices:
proxy = SharedProxy(country="us")
proxy = "socks5://username:password@proxy.example.com:1080"
```

`client.proxies` exposes location lookups and quota calls. See [Proxies](/proxies) for tier availability and targeting rules.

## Profiles

Create a profile and save its UUID for later use:

```python theme={null}
from surfsky import Fingerprint, StorageOptions

profile = await client.profiles.create(
    title="my profile",
    fingerprint=Fingerprint(os="win"),
    storage_options=StorageOptions(cookies=True, localstorage=True),
)
async with client.browser(profile_uuid=profile.uuid) as browser:
    await browser.goto("https://example.com")
```

Run this inside an `AsyncSurfsky` client context. Start the same UUID on subsequent runs; do not create a new profile for each job. [Sessions](/sessions#reuse-a-login) describes the login-and-reuse workflow.

## Use Playwright

Use `client.session()` to let the SDK manage the session while Playwright handles page operations. Install `surfsky` and `playwright`, and set the environment variables before running this example:

```python theme={null}
from playwright.sync_api import sync_playwright
from surfsky import Surfsky

with Surfsky() as client, client.session() as session, sync_playwright() as pw:
    browser = pw.chromium.connect_over_cdp(session.connect_url)
    try:
        page = browser.contexts[0].pages[0]
        page.goto("https://example.com")
        print(page.title())
    finally:
        browser.close()
```

The session context stops the remote browser when the block exits.

<span id="escape-hatch" />

<span id="full-reference" />

## Raw requests

```python theme={null}
response = await client.request("GET", "/proxies/shared/quota")
response.raise_for_status()
print(response.json())
```

`request()` returns an `httpx.Response`. Unlike resource methods, it does not raise for an HTTP error unless you call `raise_for_status()`.

The [SDK repository](https://github.com/surfskyio/surfsky-py) includes the full method reference and additional examples.
