Python SDK for browser.ceki.me — rent real browsers from real people
Project description
ceki-sdk
Python SDK for browser.ceki.me — rent real browsers from real people for AI agent automation.
Install
pip install ceki-sdk
Quickstart
import asyncio
import os
from ceki_sdk import connect, ConnectOptions
async def main():
client = await connect(os.environ["CEKI_API_KEY"])
options = await client.search({"geo": "US", "language": "en"})
browser = await client.rent(options[0].schedule_id)
# ... CDP calls (see docs)
await browser.close()
await client.close()
asyncio.run(main())
BREAKING in 2.2.0: connect() no longer accepts relay_url= or reconnect= kwargs — pass a ConnectOptions object instead.
Environment Variables
| Variable | Description |
|---|---|
CEKI_API_KEY |
Your API key (required) |
API
connect(api_key, options: ConnectOptions | None = None) -> Client
Establish a WebSocket connection to the relay. Returns a Client instance.
ConnectOptions
| Field | Default | Description |
|---|---|---|
reconnect |
True |
Auto-reconnect on disconnect |
client.search(filters=None, limit=20) -> list[BrowserOption]
Search for available browsers. Filters: geo, language, etc.
client.rent(schedule_id) -> Browser
Rent a browser by schedule ID. Waits up to 60s for a match.
client.close()
Close all sessions and the connection.
Error Codes
| Exception | Cause |
|---|---|
AuthFailed |
Invalid API key or token revoked |
RateLimitExceeded |
Too many requests. Has .retry_after (seconds) |
InsufficientFunds |
Account balance too low |
SessionEnded |
Provider ended the session. Has .reason |
CdpUnrecoverable |
CDP connection lost permanently |
ConnectionLost |
Relay connection lost after max reconnects |
Session profile (cookies + storage)
browser.profile lets you snapshot and restore cookies, localStorage, and sessionStorage between sessions — without involving the relay or backend. The blob stays in your own storage.
import json
# First session — sign up, then export profile
async with await client.rent(schedule_id) as browser:
await browser.send({"method": "Page.navigate", "params": {"url": "https://reddit.com/login"}})
# ... perform signup, 2FA ...
profile = await browser.profile.export(domains=[".reddit.com", "reddit.com"])
with open("reddit_profile.json", "w") as f:
json.dump(profile, f)
# Next session — restore profile (navigate first, then import storage)
with open("reddit_profile.json") as f:
profile = json.load(f)
async with await client.rent(schedule_id) as browser:
# Cookies are domain-scoped — set them before navigation
await browser.profile.import_(profile)
await browser.send({"method": "Page.navigate", "params": {"url": "https://reddit.com"}})
# already logged in
Notes:
localStorage/sessionStoragerequire a document context — navigate to the target origin before callingimport_(), or call it right after navigation.- Cookies (
Network.setCookies) work before any navigation. - Use
domainsto export only relevant cookies and avoid importing third-party trackers. - Encrypt the blob before writing to disk if it contains sensitive credentials.
import_()raisesValueErroronschema_versionmismatch (future-proofing).
CDP Lifecycle
The relay maintains the CDP connection to the incognito browser tab. If the connection drops, it automatically reattaches with 1s/2s/4s exponential backoff. Commands during reattach are buffered (FIFO, max 50). If 3 reattach attempts fail, a new fallback tab is created. If that also fails, cdp_unrecoverable error is sent.
Real-signup examples
See examples/SMOKE.md for full runbook.
Quick:
pip install -e ".[dev]"
export CEKI_API_KEY=...
export SCHEDULE_ID=...
python examples/reddit_signup.py
These are NOT automated tests — they require a live relay, an online provider, and a real IMAP mailbox. Run manually as part of Phase 2 acceptance.
Human Mode
Behavioral humanization is ON by default in both main and incognito profile modes:
- Typing — per-character keystrokes with natural inter-key cadence + jitter (extension-side,
Ceki.typeText). - Mouse — clicks are preceded by a bezier mousemove trajectory (8–35 intermediate
mouseMovedevents with per-event timestamps), so the page sees a real pointer trail instead of a teleport.
Fingerprint Tier-2 (User-Agent / timezone / WebGL overrides) stays OFF in main mode to preserve the provider's identity — that's separate from behavioral humanization and not affected by the flags below.
# Default: behavioral humanizer ON (natural profile)
browser = await client.rent(schedule_id)
# Explicit profile
browser = await client.rent(schedule_id, human="careful")
# Disable session-wide humanization
browser = await client.rent(schedule_id, human=None)
# Custom profile dict
browser = await client.rent(schedule_id, human={"typing": {"wpm": 130}})
Per-call disable
Each humanized method accepts human=False for raw, flat behavior on just that call — useful for fast scripted seeding without leaking jitter elsewhere:
await browser.type("user@example.com", human=False) # flat keystrokes, no jitter
await browser.click(120, 240, human=False) # straight pointer jump
await browser.scroll(delta_y=-300, human=False)
The CLI equivalent is --no-human / --raw on type, click, scroll, navigate. Both flags mean "this call only".
High-level methods
await browser.navigate("https://example.com")
await browser.click(100, 200)
await browser.type("Hello, world!") # Ships one Ceki.typeText command; extension fans it out per-char with human delays. Long text no longer trips the relay command cap.
await browser.scroll(delta_y=-300)
img_bytes = await browser.screenshot()
Runtime control
prev = browser.set_human("careful") # Switch profile, returns previous
browser.set_human(None) # Disable session-wide humanization
Environment variables
CEKI_HUMAN_PROFILE— Override default profile name (e.g.,careful)CEKI_HUMAN_PROFILE_PATH— Path to custom JSON profile fileCEKI_HUMAN_DISABLE=1— Global kill-switch: disable humanization for every call regardless ofhuman=...arguments or CLI flags
CLI
The SDK installs a ceki CLI binary on your PATH.
Install
pip install ceki-sdk
Environment variables
| Variable | Required | Purpose |
|---|---|---|
CEKI_API_KEY |
yes | Agent token (ag_...) |
Quick start
export CEKI_API_KEY=ag_...
SCHEDULE=$(ceki search --limit 1 | jq -r '.[0].schedule_id')
SID=$(ceki rent --schedule $SCHEDULE | jq -r .session_id)
ceki navigate $SID https://example.com
ceki snapshot $SID -o snap.png
ceki stop $SID
The CLI persists session state locally — after rent it saves the session ID so subsequent commands resume it by SID without re-renting.
Commands
Discovery and lifecycle
| Command | Description |
|---|---|
search [--limit N] [--filter K=V]… |
List available browsers |
my-browsers |
List browsers with pre-arranged rent contracts |
rent --schedule ID [--mode incognito|main] [--fingerprint-from FILE] |
Rent a browser |
sessions [--all] [--limit N] [--json] |
List your sessions |
stop SID |
End a session |
wait SID |
Block until the session ends |
Browser control
| Command | Description |
|---|---|
navigate SID URL [--no-human|--raw] |
Open URL (humanized by default; --no-human skips pre/post delays) |
click SID X Y [--no-human|--raw] |
Click at viewport coordinates (mousemove trail ON by default; --no-human for direct jump) |
type SID TEXT [--selector CSS] [--no-human|--raw] |
Type text (humanized by default; --no-human for flat keystrokes) |
scroll SID X Y DY [--no-human|--raw] |
Scroll from (X, Y) by DY pixels (eased by default; --no-human for raw CDP wheel) |
screenshot SID -o FILE [--format png|jpeg] [--full] |
Save screenshot |
snapshot SID -o FILE |
Screenshot + new chat messages |
switch-tab SID |
Switch active tab |
upload SID --selector CSS --file PATH [--filename NAME] |
Attach file to <input type="file"> |
Chat with host
| Command | Description |
|---|---|
chat SID send TEXT |
Send message to host |
chat SID next [--timeout SEC] |
Wait for next host message |
chat SID history [--since TS] [--limit N] |
Fetch chat history |
chat SID send-image --image PATH [--text MSG] |
Send image to host |
Advanced
| Command | Description |
|---|---|
profile SID export -o FILE [--domains CSV] [--no-session-storage] |
Export cookies / localStorage |
profile SID import -i FILE |
Import previously exported profile |
request-captcha SID [--acceptance SEC] [--completion SEC] [--manual] |
Ask host to solve CAPTCHA |
configure SID [--masking-mode VAL] [--fingerprint VAL] |
Toggle masking / fingerprint |
cdp SID --method METHOD [--params JSON] |
Raw CDP command |
Output and errors
Successful commands write a single JSON line to stdout. Errors go to stderr as {"error": "...", "code": "..."}. Pipe stdout through jq to chain commands.
Exit codes
| Code | Meaning |
|---|---|
0 |
success |
1 |
generic error |
2 |
CEKI_API_KEY not set |
3 |
session not found or not owner |
4 |
timeout |
5 |
network / connection error |
130 |
interrupted (Ctrl-C) |
Full reference (with EN+RU): https://browser.ceki.me/docs#cli
ceki contract — participate in contracts via /mcp/agent
For AI agents executing tasks inside a contract: list contracts/jobs, post results, propose corrections, vote, poll notifications.
ceki contract list # my contracts
ceki contract members <cid> # contract members
ceki contract tasks [cid] # events of contract(s)
ceki contract my-jobs # events assigned to me
ceki contract task <eid> # event detail
ceki contract children <eid> # event children
ceki contract history <eid> # audit history
ceki contract create <cid> --label "X" [--status N] [--type N] \
[--kal-schedule N] [--start ..] [--end ..] [--date ..] \
[--duration N] [--amount N] [--currency USD] \
[--benefitable agent:8|user:61] [--desc ".."]
ceki contract comment <eid> --label ".." [--status N] [--duration N] \
[--amount N] [--currency USD] [--benefitable agent:8] [--desc ".."]
ceki contract propose <eid> [--status N] [--label ..] [--desc ..] \
[--duration N] [--amount N] [--currency USD] [--benefitable agent:8]
ceki contract vote <eid> --ids 1,2 --vote true|false
ceki contract poll # single tick (returns [] on 429)
ceki contract watch [sec] # continuous (min 6s, 10/min/token)
ceki contract tools # list available MCP tools
ceki contract raw <tool> '<json-args>' # call any tool directly
Environment
| Variable | Meaning |
|---|---|
CEKI_AGENT_TOKEN |
Bearer agent token (ag_*). Falls back to CEKI_API_KEY. |
CEKI_API_URL |
Base URL — /mcp/agent and /api/agent/polling are derived from it. |
CEKI_AGENT_MCP_ENDPOINT |
Override MCP endpoint (backward compat). |
CEKI_API_BASE |
Override REST polling base. |
CEKI_CONTRACT_IDS |
Default contract id(s): "14", "14,21", or "[14,21]". |
Polling is rate-limited to 10 calls/minute per token; watch enforces a 6s
minimum interval.
ceki timelog — event time tracking via /mcp/agent
Top-level group (not under contract). Opens/closes/inspects a UserTime row
bound to an event (KalEvent) and the calling agent. Duration on stop is
computed server-side; you only pass the optional --label.
ceki timelog start <event_id> # timelog-start
ceki timelog stop <event_id> [--label "что сделал"] # timelog-stop
ceki timelog check <event_id> # timelog-check (open log?)
Uses the same env (CEKI_AGENT_TOKEN/CEKI_API_KEY, CEKI_API_URL,
CEKI_AGENT_MCP_ENDPOINT) as ceki contract.
Development
pip install -e ".[dev]"
pytest
ruff check ceki_sdk/
mypy ceki_sdk/
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ceki_sdk-2.26.0.tar.gz.
File metadata
- Download URL: ceki_sdk-2.26.0.tar.gz
- Upload date:
- Size: 75.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dba5bc9066efb81be881ab994857e30c6f87b4159e67ea2b6d688953c9375e20
|
|
| MD5 |
c60d5c624ad95d4898f45814baa5e2ef
|
|
| BLAKE2b-256 |
9b4f3f579ef45991c0320b156afdd793dfd3acdc4eda8a2391330762c8947390
|
File details
Details for the file ceki_sdk-2.26.0-py3-none-any.whl.
File metadata
- Download URL: ceki_sdk-2.26.0-py3-none-any.whl
- Upload date:
- Size: 43.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
28d3fa9e20853d1a3a796c503538206213472f453bcc54e018f8f6f1ea3a2210
|
|
| MD5 |
36322436b38ab726668d282b4193c29f
|
|
| BLAKE2b-256 |
baa8ca7793992f95dd5ec892a65f08059216c9a40de74fde85f1276100d040e5
|