TypeScript/Node.js SDK for browser.ceki.me — rent real browsers from real people for AI agent automation.
npm install @ceki/sdkFor the CLI (global):
npm install -g @ceki/sdkimport { connect } from '@ceki/sdk';
const client = await connect(process.env.CEKI_API_KEY!);
const options = await client.search({ geo: 'US', language: 'en' });
const browser = await client.rent(options[0].schedule_id);
await browser.navigate('https://example.com');
const snap = await browser.snapshot();
// snap.screenshot — base64 PNG, snap.chat — new messages
await browser.close();
await client.close();| Variable | Description |
|---|---|
CEKI_API_KEY |
Your API key (required) |
Establish a WebSocket connection to the relay. Returns a Client instance.
| Field | Default | Description |
|---|---|---|
reconnect |
true |
Auto-reconnect on disconnect |
Search for available browsers. Filters: geo, language, etc.
Rent a browser by schedule ID. Waits up to 60s for a match. Options:
human—'natural'(default),'careful', ornull(no humanization)maskingMode— enable maskingfingerprint—true,false, or fingerprint object
Resume an existing session within its 120s grace window.
Close all sessions and the connection.
await browser.navigate(url) // Navigate to URL
await browser.click(x, y) // Click at coordinates
await browser.type(text) // Type text — one Ceki.typeText command; extension does per-char keydown/keyUp + humanizer delays
await browser.scroll({ deltaY: -300 }) // Scroll
await browser.screenshot({ format: 'png' }) // Screenshot as Buffer
await browser.screenshot({ format: 'base64' }) // Screenshot as {data: string}
await browser.snapshot() // Screenshot + chat history
await browser.switchTab() // Switch browser tab
await browser.configure({ maskingMode: true }) // Configure session
await browser.upload(selector, pathOrBuffer) // Upload file to input
await browser.send({ method, params }) // Raw CDP command
await browser.close() // End session (alias: release)
await browser.waitUntilEnded() // Block until session endsawait browser.chat.send('Please solve the captcha')
await browser.chat.sendImage('/tmp/screenshot.png')
const messages = await browser.chat.history({ since: ts, limit: 50 })
browser.chat.onMessage(msg => console.log(msg.text))// Export profile
const profile = await browser.profile.export({
domains: ['.reddit.com', 'reddit.com'],
});
fs.writeFileSync('profile.json', JSON.stringify(profile));
// Import profile in next session
const saved = JSON.parse(fs.readFileSync('profile.json', 'utf-8'));
await browser.profile.import(saved);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), so the page sees a real pointer trail instead of a teleport.
Fingerprint Tier-2 (UA / timezone / WebGL overrides) stays OFF in main mode to preserve the provider's identity — separate from behavioral humanization.
// Default: behavioral humanizer ON (natural profile)
const browser = await client.rent(scheduleId);
// Explicit profile
const browser = await client.rent(scheduleId, { human: 'careful' });
// Disable session-wide humanization
const browser = await client.rent(scheduleId, { human: null });Pass { human: false } to flatten just one call, without leaking jitter to siblings:
await browser.type('user@example.com', { human: false }); // flat keystrokes
await browser.click(120, 240, { human: false }); // straight pointer jump
await browser.scroll({ deltaY: -300, human: false });The CLI equivalent is --no-human / --raw on type, click, scroll, navigate. Both flags mean "this call only".
CEKI_HUMAN_PROFILE— Override default profile name (careful)CEKI_HUMAN_PROFILE_PATH— Path to custom JSON profile fileCEKI_HUMAN_DISABLE=1— Global kill-switch: disable humanization for every call regardless of per-callhuman:arguments or CLI flags
| Exception | Cause |
|---|---|
AuthError |
Invalid API key or token revoked |
RateLimitExceeded |
Too many requests. Has .retryAfter (seconds) |
InsufficientFunds |
Account balance too low |
SessionEnded |
Provider ended the session. Has .reason |
SessionNotFound |
Session ID not found |
SessionExpired |
Session grace window expired |
NotOwner |
Not the session owner |
CdpUnrecoverable |
CDP connection lost permanently |
ConnectionLost |
Relay connection lost after max reconnects |
TimeoutError |
Operation timed out |
TransportError |
WebSocket or HTTP transport error |
ChatSendFailed |
Chat message failed to send |
The SDK installs a ceki CLI binary on your PATH.
npm install -g @ceki/sdk| Variable | Required | Purpose |
|---|---|---|
CEKI_API_KEY |
yes | Agent token (ag_...) |
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 $SIDThe CLI persists session state locally — after rent it saves the session ID so subsequent commands resume it by SID without re-renting.
| 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 |
| 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"> |
| 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 |
| 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 |
Successful commands write a single JSON line to stdout. Errors go to stderr as {"error": "...", "code": "..."}. Pipe stdout through jq to chain commands.
| 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
npm install
npm run typecheck
npm test
npm run buildMIT