Hyperbrowser Python SDK
Checkout the full documentation here
Installation
Currently Hyperbrowser supports creating a browser session in two ways:
- Async Client
- Sync Client
It can be installed from pypi by running :
pip install hyperbrowser
The browser-control examples below also use Playwright:
pip install playwright
Configuration
Both the sync and async client follow similar configuration params
API Key
The API key can be configured either from the constructor arguments or environment variables using HYPERBROWSER_API_KEY
If no API key is provided, the client falls back to a saved OAuth session created by hx auth login. By default it reads ~/.hx_config/auth/default.json, or ~/.hx_config/auth/<profile>.json when HYPERBROWSER_PROFILE or ClientConfig(profile=...) is set.
Profile names must match ^[A-Za-z0-9._-]+$.
base_url and HYPERBROWSER_BASE_URL accept either https://host or https://host/api. The client normalizes both to the same control-plane base URL.
Usage
Hyperbrowser 1.0 accepts plain dictionaries for request parameters. Method
signatures use TypedDict definitions, so editors can autocomplete keys at
every nested level:
from hyperbrowser import Hyperbrowser
client = Hyperbrowser(api_key="test-key")
# Preferred in 1.0: autocomplete works directly in the dictionary.
session = client.sessions.create(
{
"use_stealth": True,
"screen": {"width": 1920, "height": 1080},
}
)
Browser sessions can also use an outbound network policy. Omitting these fields keeps the default unrestricted behavior:
session = client.sessions.create(
{
"allow_internet_access": False,
"allow_out": ["example.com"],
"deny_out": ["0.0.0.0/0"],
}
)
Direct browser policies accept domains, IPv4 addresses, and CIDR ranges in
allow_out; deny_out accepts IPv4 addresses and CIDR ranges. With a proxy,
allow rules must be domains and the only supported deny rule is 0.0.0.0/0.
Existing Pydantic request classes remain accepted, so upgrading does not require an immediate rewrite:
from hyperbrowser.models import CreateSessionParams, ScreenConfig
session = client.sessions.create(
CreateSessionParams(
use_stealth=True,
screen=ScreenConfig(width=1920, height=1080),
)
)
Import request annotations from hyperbrowser.types when a named variable is
useful. The same names under hyperbrowser.models refer to the legacy Pydantic
request classes. Responses remain Pydantic models.
JSON Schema fields accept raw schema values, including object schemas with
$defs, $ref, or custom keywords, and boolean schemas where the API supports
them. Those schemas and other user-owned mappings are preserved as data; only
SDK-owned request keys are translated to their API aliases. Schema fields
documented as accepting a model class can also generate a schema from a Pydantic
model.
See the Hyperbrowser Python SDK 1.0 migration guide for the complete compatibility details and migration checklist.
Async
import asyncio
from hyperbrowser import AsyncHyperbrowser
from playwright.async_api import async_playwright
HYPERBROWSER_API_KEY = "test-key"
async def main():
async with AsyncHyperbrowser(api_key=HYPERBROWSER_API_KEY) as client:
session = await client.sessions.create()
try:
async with async_playwright() as playwright:
browser = await playwright.chromium.connect_over_cdp(
session.ws_endpoint
)
context = browser.contexts[0]
page = context.pages[0]
print("Navigating to Hacker News...")
await page.goto("https://news.ycombinator.com/")
print("Page title:", await page.title())
finally:
await client.sessions.stop(session.id)
# Run the asyncio event loop
asyncio.run(main())
Sync
from playwright.sync_api import sync_playwright
from hyperbrowser import Hyperbrowser
HYPERBROWSER_API_KEY = "test-key"
def main():
client = Hyperbrowser(api_key=HYPERBROWSER_API_KEY)
session = client.sessions.create()
ws_endpoint = session.ws_endpoint
# Launch Playwright and connect to the remote browser
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(ws_endpoint)
context = browser.new_context()
# Get the first page or create a new one
if len(context.pages) == 0:
page = context.new_page()
else:
page = context.pages[0]
# Navigate to a website
print("Navigating to Hacker News...")
page.goto("https://news.ycombinator.com/")
page_title = page.title()
print("Page title:", page_title)
page.close()
browser.close()
print("Session completed!")
client.sessions.stop(session.id)
# Run the asyncio event loop
main()
Sandboxes
The sync and async clients expose the same sandbox APIs through client.sandboxes.
Create a sandbox with pre-exposed ports
from hyperbrowser import Hyperbrowser
client = Hyperbrowser(api_key="test-key")
sandbox = client.sandboxes.create(
{
"image_name": "node",
"cpu": 2,
"memory_mib": 2048,
"disk_mib": 8192,
"exposed_ports": [{"port": 3000, "auth": True}],
}
)
print(sandbox.exposed_ports[0].browser_url)
print(sandbox.cpu, sandbox.memory_mib, sandbox.disk_mib)
sandbox.stop()
client.close()
cpu, memory_mib, and disk_mib are only supported for image launches.
Manage volumes and mount them in a sandbox
from hyperbrowser import Hyperbrowser
client = Hyperbrowser(api_key="test-key")
volume = client.volumes.create({"name": "project-cache"})
all_volumes = client.volumes.list()
same_volume = client.volumes.get(volume.id)
sandbox = client.sandboxes.create(
{
"image_name": "node",
"mounts": {
"/workspace/cache": {
"id": same_volume.id,
"type": "rw",
"shared": True,
}
},
}
)
sandbox.stop()
client.volumes.delete(same_volume.id)
client.close()
List sandboxes with filters
from hyperbrowser import Hyperbrowser
client = Hyperbrowser(api_key="test-key")
result = client.sandboxes.list(
{
"status": "active",
"search": "sandbox",
"start": 1711929600000,
"end": 1712016000000,
"limit": 20,
}
)
for sandbox in result.sandboxes:
print(sandbox.id, sandbox.status)
List snapshots for a specific image
from hyperbrowser import Hyperbrowser
client = Hyperbrowser(api_key="test-key")
snapshots = client.sandboxes.list_snapshots(
{"image_name": "node", "status": "created", "limit": 10}
)
Expose and unexpose ports
from hyperbrowser import Hyperbrowser
client = Hyperbrowser(api_key="test-key")
sandbox = client.sandboxes.create(
{"image_name": "node", "cpu": 2, "memory_mib": 2048, "disk_mib": 8192}
)
result = sandbox.expose({"port": 8080, "auth": True})
print(result.url, result.browser_url)
sandbox.unexpose(8080)
Batch file writes with per-file options
from hyperbrowser import Hyperbrowser
client = Hyperbrowser(api_key="test-key")
sandbox = client.sandboxes.create({"image_name": "node"})
sandbox.files.write(
[
{
"path": "/tmp/config.json",
"data": '{"debug":true}\n',
"append": True,
"mode": "600",
},
{"path": "/tmp/blob.bin", "data": b"\x00\x01\x02"},
]
)
Run commands and collect output
sandbox.exec() and sandbox.processes.start() stream command output from the
receiver as soon as execution starts. The SDK collects stdout and stderr in
memory, so a completed result is not limited to the receiver's replay buffer.
This requires a receiver supporting streaming POST /sandbox/processes; roll
out the receiver before upgrading the SDK.
result = sandbox.exec("make test", max_output_bytes=128 * 1024 * 1024)
print(result.stdout, result.stderr, result.exit_code)
process = sandbox.processes.start("make test")
try:
for event in process.stream():
if event.type == "stdout":
print(event.data, end="")
result = process.wait()
finally:
process.disconnect()
The combined output limit defaults to 64 MiB per command and can be adjusted
with max_output_bytes. Exceeding it raises output_limit_exceeded. A broken
stream, missing output, or receiver truncation raises incomplete_output.
These errors include the process ID and do not automatically rerun the command.
Process streams use a separate 60-second read-idle timeout once response headers arrive. Output and the receiver's 15-second heartbeats reset this timeout, so quiet commands can run longer than the client's ordinary HTTP timeout. That ordinary timeout still applies to connection setup and waiting for response headers.
start() returns after the process starts and collects in the background.
wait(timeout_sec=...) limits the local wait; collection continues after a wait
timeout. The timeout passed to start() or exec() limits command execution.
A local wait timeout raises TimeoutError (asyncio.TimeoutError in the async API).
disconnect() stops collection and leaves the command running. Use kill() to
stop it. Reattaching with get() can retrieve only retained receiver output;
wait() raises if that output has been truncated.
The async API has the same behavior: await exec(), start(), wait(), and
disconnect(), and use async for with stream().
Resume terminal output after reconnect
from hyperbrowser import Hyperbrowser
client = Hyperbrowser(api_key="test-key")
sandbox = client.sandboxes.create({"image_name": "node"})
terminal = sandbox.terminal.create({"command": "bash"})
connection = terminal.attach(cursor=10)
for event in connection.events():
print(event)
Cache remote Dockerfile builds
Use the public context fingerprint when deriving a cache name. It uses the same
Dockerfile source selection and .dockerignore rules as remote packaging,
including file contents, modes, paths, and symlinks. It ignores timestamps and
does not compress or stage the context on disk.
from hyperbrowser.build_context import docker_build_context_fingerprint
fingerprint = docker_build_context_fingerprint("./app")
# Include build options such as platform and image_init in your cache key too.
image_name = f"app-{fingerprint[:32]}"
build = client.sandboxes.build_image_from_dockerfile(
context_path="./app",
image_name=image_name,
expected_context_fingerprint=fingerprint,
)
If the archived inputs differ from the fingerprint, the SDK raises
DockerBuildContextChangedError before creating or uploading a build. Compute a
fresh fingerprint and repeat the lookup/build operation. Use the same dockerfile
and force_full_context selection when fingerprinting and building (the latter
is named remote_full_context on the build method). The expected fingerprint is
supported only for remote builds.
Fingerprinting streams included files and performs blocking I/O. Async callers
should use await asyncio.to_thread(docker_build_context_fingerprint, "./app")
(Python 3.9+) or an executor. It does not resolve mutable base-image tags or
network resources fetched by a Dockerfile; rebuild explicitly when those change.
Image listings distinguish ready from uploaded: a completed team image can
be ready to launch before its durability backup is uploaded. ready is None
when talking to an older server. Keep the returned image ID to pin that revision.
Reuse an image or join a build
get_or_build_image provides the same operation on sync and async clients. Give
it either a remote Dockerfile context or a local Docker image. It derives a name
from the input identity and image initialization options, reuses a ready team
image, or submits a build and joins a compatible concurrent build automatically.
The optional prefix is a namespace, not a fixed image alias: different inputs
produce different names under the same prefix.
resolved = client.sandboxes.get_or_build_image(
context_path="./app", # alternatively: docker_image="local/app:latest"
image_name_prefix="my-app",
wait_timeout=3600,
)
print(resolved.outcome) # "reused", "joined", or "created"
sandbox = client.sandboxes.create({
"image_name": resolved.image_name,
"image_id": resolved.image_id,
})
With wait=False, a submitted/joined build is returned as resolved.build;
image_id is populated only when ready. find_ready_image(name) exposes the
exact-name lookup separately. Older servers fall back to uploaded-image reuse.
The public hyperbrowser.image_builds.image_build_name helper lets integrations
derive the same name from an existing context fingerprint or Docker image digest.
Passing expected_context_fingerprint or expected_image_digest avoids repeating
identity discovery; supply a fresh identity for each resolution request. Changes
between identity discovery and packaging are rejected instead of published under
the wrong name. Local Docker images must already be available in the daemon.
Automatic local-image identity discovery requires a Docker CLI and Engine
supporting API 1.49 or newer (Docker 28.1+) for platform-specific inspection.
Upgrade Docker and check for an older DOCKER_API_VERSION override if the helper
reports this requirement. Remote Dockerfile builds do not require local Docker.
The existing explicit-name import method retains its inspection fallback.
force_build=True skips ready-image lookup but still joins matching active builds
and permits existing layer/artifact caches. Use it to refresh mutable base tags or
external Dockerfile downloads. Joining does not change an existing builder's
resources. Lookup and creation use separate API calls; if another build completes
between them, an additional revision can be submitted.
Each caller owns its polling timeout. Canceling that wait does not cancel an
accepted backend build. Uploads have a separate inactivity allowance
(upload_timeout=600 by default), not a total upload-duration limit. The existing
build_image_from_dockerfile and build_image_from_docker_image methods retain
their explicit-name behavior and continue to report build conflicts directly.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Release files for hyperbrowser 1.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hyperbrowser-1.9.0.tar.gz | 129.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hyperbrowser-1.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 318.1 kB
Release files / hyperbrowser-1.9.0.tar.gz
| Download URL | hyperbrowser-1.9.0.tar.gz |
|---|---|
| Size | 129.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9bc49e41db95b7869d88d35dd7a53d5b3fa82e0af255d89eb2f7ffce747cd000
|
|
BLAKE2b-256 checksum How to use checksums |
7d115b5d0528f113b8193839326cf8d579e49fcee39355d86ecd61d65e270a25
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency logRelease files / hyperbrowser-1.9.0-py3-none-any.whl
| Download URL | hyperbrowser-1.9.0-py3-none-any.whl |
|---|---|
| Size | 188.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2a6f835dae128d448d590d8c0a9e73021106c203931784ea30bf54c0d806c9ef
|
|
BLAKE2b-256 checksum How to use checksums |
980c88e78e974c0253523b0b81caa61ed463bde327c73422174f052ca1062dd5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency log