Skip to content

Ceki-me/python-sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

87 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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/sessionStorage require a document context — navigate to the target origin before calling import_(), or call it right after navigation.
  • Cookies (Network.setCookies) work before any navigation.
  • Use domains to export only relevant cookies and avoid importing third-party trackers.
  • Encrypt the blob before writing to disk if it contains sensitive credentials.
  • import_() raises ValueError on schema_version mismatch (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 mouseMoved events 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 file
  • CEKI_HUMAN_DISABLE=1Global kill-switch: disable humanization for every call regardless of human=... 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/

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages