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

# Python

> Launch Apostate from Python with its entry points, options, Playwright options, async API, several browsers at once, warnings and diagnostics, drivers and errors.

The Python package (`apostate` on PyPI) finds or downloads the browser, looks up the proxy's exit, and starts the browser through Patchright, a Playwright build. It returns Playwright objects, so the rest of your script is ordinary Playwright code. [Installation](/installation) covers `pip install apostate` and the browser download.

## Launch a browser

```python theme={null}
from apostate import launch

browser = launch(
    fingerprint=42,
    fingerprint_platform="windows",
    locale="de-DE",
    timezone="Europe/Berlin",
)
page = browser.new_page()
page.goto("https://example.com")
print(page.evaluate("""() => ({
    platform: navigator.platform,
    languages: navigator.languages,
    timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
})"""))
browser.close()
```

```text theme={null}
{'platform': 'Win32', 'languages': ['de-DE', 'de', 'en-US', 'en'], 'timeZone': 'Europe/Berlin'}
```

`fingerprint` is the seed. The same seed and persona give the same machine on every launch. `fingerprint_platform` is the persona, the operating system the browser presents. [Personas](/concepts/personas) and [Seeds and identity](/concepts/seeds-and-identity) explain both.

`launch()` returns Playwright's `Browser`. The browser runs on a temporary normal profile, which is deleted when the browser closes. `new_page()` opens pages in that profile. The first call returns the blank tab the browser started with.

<Warning>
  `new_context()` opens an off-the-record context, as it does in Playwright, and sites can tell an off-the-record context from a normal profile. Open pages with `new_page()`.
</Warning>

`new_page()` takes no options, because every page shares the one profile. Pass page options such as `viewport` or `color_scheme` to `launch()` instead.

## Entry points

| Function                                   | Returns                                   | Profile                                     |
| ------------------------------------------ | ----------------------------------------- | ------------------------------------------- |
| `launch()`                                 | Playwright `Browser`                      | Temporary, deleted on close                 |
| `launch_context()`                         | The temporary profile's `BrowserContext`  | Temporary, deleted on close                 |
| `launch_persistent_context(user_data_dir)` | A `BrowserContext` bound to the directory | Kept, with its cookies, storage and machine |

Each has an async version: `launch_async()`, `launch_context_async()` and `launch_persistent_context_async()`.

`launch_context()` takes the same options as `launch()`, plus `context_options`, which apply to the context when it starts. Closing the context closes the browser.

```python theme={null}
from apostate import launch_context

context = launch_context(
    fingerprint=42,
    fingerprint_platform="windows",
    context_options={"color_scheme": "dark"},
)
page = context.new_page()
page.goto("https://example.com")
print(page.evaluate("matchMedia('(prefers-color-scheme: dark)').matches"))
context.close()  # closes the browser and deletes its profile
```

```text theme={null}
True
```

`launch_persistent_context()` keeps a profile in a directory. The first launch stores a seed in `DIR/apostate/identity`, and every later launch with that directory presents the same machine. `launch()` refuses a user data directory.

```python theme={null}
from apostate import launch_persistent_context

with launch_persistent_context("./profiles/shop-account", fingerprint_platform="windows") as context:
    page = context.new_page()
    page.goto("https://example.com")
    print(page.evaluate("[navigator.hardwareConcurrency, screen.width, screen.height]"))
```

Two runs of this script print the same values. On the host used here:

```text theme={null}
[8, 1920, 1080]
```

## Async API

```python theme={null}
import asyncio

from apostate import launch_async


async def main():
    async with await launch_async(fingerprint=42, fingerprint_platform="windows") as browser:
        page = await browser.new_page()
        await page.goto("https://example.com")
        print(await page.evaluate("navigator.platform"))


asyncio.run(main())
```

```text theme={null}
Win32
```

The async functions take the same options as the sync ones.

## Several browsers at once

Give each browser its own seed or its own user data directory. Two browsers cannot open one user data directory at the same time, and the second launch fails with a `LaunchError` that names a `ProcessSingleton`.

From 0.4.4, the sync API can keep several browsers open in one thread. They share the thread's driver, and the last `close()` stops it.

```python theme={null}
from apostate import launch

browsers = [launch(fingerprint=seed, fingerprint_platform="windows") for seed in (1, 2, 3)]
for browser in browsers:
    page = browser.new_page()
    page.goto("https://example.com")
    print(page.evaluate("[navigator.hardwareConcurrency, screen.width, screen.height]"))
for browser in browsers:
    browser.close()
```

On a Mac with 14 cores:

```text theme={null}
[12, 1280, 1024]
[12, 1920, 1080]
[12, 1920, 1080]
```

In 0.4.3, a second `launch()` while the first browser is still open fails with `It looks like you are using Playwright Sync API inside the asyncio loop`. On 0.4.3, use the async API, which runs several browsers from one event loop. [Many sessions](/guides/many-sessions) runs them with `asyncio.gather()` and a limit on how many are open.

## Options

| Option                 | Default                                          | Effect                                                                                                        |
| ---------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
| `fingerprint`          | A new seed per launch                            | The seed, an integer or a string of up to 512 bytes of `A-Z a-z 0-9 . _ : -`. `"host"` turns the persona off. |
| `fingerprint_platform` | `macos` on a Mac, `windows` on Windows and Linux | `windows`, `macos` or `linux`.                                                                                |
| `locale`               | From GeoIP, else `en-US`                         | One tag such as `de-DE`, or a list such as `de-DE,de`.                                                        |
| `timezone`             | From GeoIP, else the host's                      | An IANA name such as `Europe/Berlin`. From 0.4.4, any other name raises `ConfigurationError`.                 |
| `geoip`                | `True`                                           | Look up the exit's country and timezone before the browser starts.                                            |
| `geoip_timeout`        | `20.0`                                           | Seconds for the whole lookup.                                                                                 |
| `geoip_provider`       | The built-in lookup                              | Your own lookup function.                                                                                     |
| `proxy`                | None                                             | A proxy URL, or a mapping with `server`, `username` and `password`.                                           |
| `headless`             | `True`                                           | `False` opens a window. On a Linux host with no display, the package starts Xvfb.                             |
| `args`                 | None                                             | Extra browser switches. An unknown or misspelt `--fingerprint*` switch is refused.                            |
| `profile`              | None                                             | A profile you wrote, as a dict or a JSON file path.                                                           |
| `driver`               | `patchright`                                     | `patchright` or `playwright`.                                                                                 |
| `binary_path`          | Found or downloaded                              | The browser to run.                                                                                           |
| `cache_dir`            | The per-user cache                               | Where the browser is installed.                                                                               |

[Proxies](/guides/proxies), [Locale and timezone](/guides/locale-and-timezone), [Custom profiles](/guides/custom-profiles) and [Linux servers](/guides/linux-servers) cover the options in use. [Python API](/reference/python-api) lists every function and option, and [Switches](/reference/switches) lists what `args` accepts.

## Playwright options

Any other keyword goes to Playwright's `launch_persistent_context()`, which takes Playwright's launch options and its context options together.

```python theme={null}
from apostate import launch

with launch(
    fingerprint=42,
    fingerprint_platform="windows",
    ignore_https_errors=True,     # a Playwright context option
    permissions=["local-fonts"],  # a Playwright context option
    timeout=60_000,               # a Playwright launch option
) as browser:
    page = browser.new_page()
    page.goto("https://self-signed.badssl.com/")
    print(page.title())
```

```text theme={null}
self-signed.badssl.com
```

The package sets some of these itself:

* `executable_path`, `headless`, `args` and `user_data_dir` come from the package. Use `binary_path`, `headless` and `args` instead.
* `env` goes over the browser's environment, which the package gives `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, `LANG` and `TZ` ([Locale and timezone](/guides/locale-and-timezone#environment-variables)). Your entries win.
* `ignore_default_args` gets `--disable-component-update` added, so the driver does not pass that switch ([Widevine](/guides/widevine#keep-component-updates-on)).
* `viewport` is off unless you pass one, so the page gets the persona's real window size. A viewport of your own changes the screen a page sees ([Screen and window](/guides/screen-and-window#viewport)).
* From 0.4.4, `color_scheme` is `"null"` unless you pass one, so a page reads the persona's own light or dark theme. The 0.4.3 package left the driver's light emulation on ([Known gaps](/known-gaps#prefers-color-scheme-in-the-0-4-3-packages)).
* `proxy` comes from the package's `proxy` option.

## Warnings

From 0.4.4, the object `launch()` or `launch_persistent_context()` returns carries `apostate_diagnostics`, a dict of what the launch resolved. A context from `launch_context()` carries its browser as `context.apostate_browser`, which has it.

```python theme={null}
from apostate import launch

browser = launch(fingerprint=42, fingerprint_platform="windows", proxy="socks5://proxy.example:1080")
diagnostics = browser.apostate_diagnostics
print({key: diagnostics[key] for key in ("locale_source", "timezone_source", "locale", "timezone")})
for warning in diagnostics["warnings"]:
    print(warning)
browser.close()
```

`proxy.example` does not resolve, so the lookup fails:

```text theme={null}
{'locale_source': 'host', 'timezone_source': 'host', 'locale': None, 'timezone': None}
GeoIP lookup failed for socks5://proxy.example:1080: every GeoIP endpoint failed: ... No locale or timezone is sent, so the persona uses en-US and the host's timezone. Behind a proxy that is the host's and not the exit's. Pass locale and timezone to match the exit.
```

| Key                                | Holds                                                                                            |
| ---------------------------------- | ------------------------------------------------------------------------------------------------ |
| `warnings`                         | The launch's warnings, as a list of strings                                                      |
| `locale`, `timezone`               | What the package sent to the browser, or `None`                                                  |
| `locale_source`, `timezone_source` | `explicit` for your own option, `geoip-derived` from the lookup, or `host` when nothing was sent |
| `platform`                         | The persona                                                                                      |
| `catalogue_version`                | The version of the catalogue tables                                                              |

The package also prints each warning to stderr as one line that starts with `apostate: `. The 0.4.3 package has no `apostate_diagnostics`, and its GeoIP warning is worded differently. To read warnings in code on 0.4.3, capture stderr during the launch:

```python theme={null}
import contextlib
import io

from apostate import launch

stderr = io.StringIO()
with contextlib.redirect_stderr(stderr):
    browser = launch(fingerprint=42, fingerprint_platform="windows")
warnings = [line for line in stderr.getvalue().splitlines() if line.startswith("apostate: ")]
browser.close()
```

`browser.apostate_driver_name` names the driver that started the browser. To see the whole machine a launch presents, run `--fingerprint-explain` ([Verify](/guides/verify)):

```bash theme={null}
apostate run -- --fingerprint=42 --fingerprint-platform=windows --fingerprint-explain
```

## Drivers

Patchright is installed with the package and is the default driver. To use Playwright instead, install it and pass `driver="playwright"`:

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

```python theme={null}
from apostate import launch

with launch(fingerprint=42, driver="playwright") as browser:
    print(browser.apostate_driver_name)
```

```text theme={null}
playwright
```

Do not run `playwright install`. Apostate brings its own browser. `driver_info()` reports what is installed and which driver a launch would use:

```python theme={null}
from apostate import driver_info

print(driver_info())
```

```text theme={null}
{'preference_order': ['patchright', 'playwright'], 'installed': ['patchright', 'playwright'], 'selected': 'patchright', 'recommended': 'patchright'}
```

[Detection](/concepts/detection) has what a page can see of each driver.

## Host mode

`fingerprint="host"` presents the host's own values and composes nothing. `"off"`, `"false"`, `"0"`, `"disable"` and `"disabled"` mean the same. Use it to tell whether a problem comes from the persona or from the host and network.

```python theme={null}
from apostate import launch

with launch(fingerprint="host") as browser:
    page = browser.new_page()
    page.goto("https://example.com")
    print(page.evaluate("navigator.platform"))
```

```text theme={null}
MacIntel
```

That output is from a Mac. Host mode refuses `fingerprint_platform` with a `ProfileError`, and the package sets no locale variables. Headless host mode reports an 800x600 screen ([Screen and window](/guides/screen-and-window#headless)).

## Close the browser

`browser.close()` closes the browser, deletes its temporary profile, and stops any Xvfb display the package started. It stops the driver too, unless another browser in the same thread still uses it. `launch()` and `launch_persistent_context()` also work as context managers, as the examples above show. Closing a context from `launch_context()` closes its browser.

Close every browser you launch. The sync driver keeps an event loop in the thread until its last browser closes.

## Errors

Every package error is a subclass of `ApostateError`.

| Exception                        | Raised when                                                                                                                                                                                                                    |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ConfigurationError`             | An option is invalid: an unknown driver, a malformed seed, a misspelt `--fingerprint*` switch, options passed to `new_page()`, a user data directory passed to `launch()`, and from 0.4.4 a timezone that is not an IANA zone. |
| `ProfileError`                   | A profile does not match the schema, host mode is combined with a persona, or a profile is combined with `--fingerprint`.                                                                                                      |
| `LaunchError`                    | The driver is missing, Xvfb is missing, or the browser exited at startup. When the browser refused a switch, the message includes its `apostate:` line, unless a `proxy` is set.                                               |
| `BinaryError` and its subclasses | The browser could not be found, downloaded or verified ([Installation](/installation)).                                                                                                                                        |
| `GeoIPError`                     | Only from `resolve_geoip()` called directly. `launch()` turns a failed lookup into a warning.                                                                                                                                  |

```python theme={null}
from apostate import ApostateError, ConfigurationError, LaunchError, launch

try:
    browser = launch(fingerprint=42, args=["--fingeprint-platform=windows"])
except ConfigurationError as exc:
    print(f"fix the options: {exc}")
except LaunchError as exc:
    print(f"the browser did not start: {exc}")
except ApostateError as exc:
    print(f"other package error: {exc}")
```

```text theme={null}
fix the options: --fingeprint-platform is not a switch this browser reads and looks like a typo of --fingerprint-platform; Chromium would ignore it and leave that surface host-inherited
```

[Errors](/reference/errors) lists the common messages and what to do about each.
