What does the GeeLark API let you control?
The GeeLark API is a POST-only JSON interface for managing cloud phones, browser profiles, apps, files, proxies, groups, tags, webhooks and billing from your own code. You authenticate every request with a traceId header plus either a Bearer token or a signed key set, then read one shared response envelope where code 0 means success. It is built for authorised, code-driven operations on devices you already control.

📌 TL;DR Executive Summary
- Core Takeaway: Every call is a POST with a JSON body and a fresh Version 4 UUID in the traceId header. Use Bearer token mode or SHA-256 key signing, then check response code 0 before you touch
data. - Key Risk/Challenge: The limit is 200 requests per minute and 24,000 per hour per API, and exceeding it may lock the API for two hours. The OpenAPI YAML covers only part of the API, so the Markdown docs stay authoritative.
- Recommended Solution: A small client with signing, a request budget with headroom, traceId logging, strict error handling, and webhooks instead of tight polling for long operations.
What the GeeLark API covers and which plan unlocks it
GeeLark presents three automation levels: Synchronizer mirrors live actions across selected profiles, RPA runs no-code workflows, and the API gives you code-level integration. This tutorial stays on the API layer, where the documented groups cover cloud phone lifecycle, browser profiles, apps, files, proxies, groups and tags, webhooks, billing and automation tasks.
That is enough to build an internal control panel, a scheduled lifecycle job, or a status dashboard for phones you own or manage for clients. Browser profiles are one of those groups, and the browser profile API runs on a separate local base URL when you work against the desktop client. Keep that distinction in your code from the first commit, or you will spend an afternoon debugging a token that was never the problem.
| Plan | API access | Profiles | Daily open/create allowance |
|---|---|---|---|
| Free | No | 2 | 10 |
| Base | Yes | 1,000 | profiles × 200 |
| Pro | Yes | 50,000 | profiles × 500 |
| Custom | Yes | Unlimited | Custom |
API access is not included on the Free plan. It starts at Base and continues on Pro and Custom, and the MCP setup needs a paid plan with API access too. Check both the profile cap and the daily open/create ceiling before you size a fleet, because those two numbers decide how much work one run can batch.
The official geelark-openapi repository ships OpenAPI 3.0 YAML in English and Chinese, plus a coverage report. That spec covers a subset only, so the Markdown docs remain the source of truth for anything missing from it. Plan your client around that gap rather than fighting it later.
Prerequisites for a working GeeLark API client
Four things have to be true before your first request returns code 0.
- API access on your plan. Free does not include it; Base, Pro and Custom do.
- Credentials in the right shape. Token mode needs an API token, which the MCP setup guide takes from GeeHub. Key mode needs an appId and an apiKey. Both modes still need a traceId on every request.
- Python 3.10+ with
requestsandpython-dotenv, plus Flask 2.0+ if you want the webhook receiver in Step 7. - The exact path and body from the Markdown docs. The OpenAPI file gives you types, not full coverage.
- A proxy decision. If your phones need a proxy, configure and test it before you debug code. This guide on whether you can run GeeLark without a proxy walks through the trade-off.
.env, add .env to .gitignore, and never paste them into a chat with an AI assistant or commit them in a shared MCP config file.
Token mode vs key mode: pick one before you write the client
Both modes travel as headers on the same POST request, and both need a fresh traceId each time. The difference is what you rotate when something leaks, and how many callers share the secret.
| Mode | Headers sent | Secret at risk | Use when |
|---|---|---|---|
| Token | traceId, Authorization: Bearer |
API token | One operator running short scripts |
| Key | traceId, appId, ts, nonce, sign |
appId + apiKey | Shared services with several callers |
Token mode is the shorter path and the right default for a script you run yourself. Key mode signs each call, so the apiKey never appears in the request headers. A captured header set is bound to one traceId, one millisecond timestamp and one nonce, which means it does not authorise a different call with a new traceId.
Whichever you choose, keep the mode in configuration rather than in code branches scattered across the file. One _headers() method that returns the right dict is easier to audit than three call sites that each build headers slightly differently.
Step 1: Create a local project and configuration files
Create a working folder and run the script below. It writes a local .env template and a .gitignore that keeps credentials and the virtual environment out of Git. Fill the values after it runs, not before you commit anything.
from pathlib import Path
env = """GEELARK_AUTH_MODE=token
GEELARK_API_TOKEN=
GEELARK_APP_ID=
GEELARK_API_KEY=
GEELARK_BASE_URL=https://openapi.geelark.com
GEELARK_BROWSER_BASE_URL=http://localhost:40185/api/v1
"""
Path(".env").write_text(env, encoding="utf-8")
Path(".gitignore").write_text(".env\n.venv/\n__pycache__/\n", encoding="utf-8")
print("Wrote .env and .gitignore")
Use token mode by setting GEELARK_AUTH_MODE=token and filling GEELARK_API_TOKEN. Switch to key mode by setting the mode to key and filling GEELARK_APP_ID and GEELARK_API_KEY. The browser base URL stays separate because it points at the local client, not the public API host.
Step 2: Send every request as POST JSON with a fresh traceId
Every call is a POST with a JSON body and a traceId header, and that traceId must be a Version 4 UUID. Generate a new one per request, log the traceId that comes back in the response, and treat that returned value as your correlation ID when an operation fails.
import json
import os
import uuid
import requests
from dotenv import load_dotenv
load_dotenv()
BASE_URL = os.getenv("GEELARK_BASE_URL", "https://openapi.geelark.com").rstrip("/")
TOKEN = os.getenv("GEELARK_API_TOKEN")
def post(path, payload):
trace_id = str(uuid.uuid4())
headers = {
"Content-Type": "application/json",
"traceId": trace_id,
"Authorization": f"Bearer {TOKEN}",
}
response = requests.post(
f"{BASE_URL}/{path.lstrip('/')}",
headers=headers,
data=json.dumps(payload),
timeout=30,
)
response.raise_for_status()
body = response.json()
print("traceId:", body.get("traceId", trace_id))
if body.get("code") != 0:
raise RuntimeError(f"{body.get('msg')} (traceId {body.get('traceId')})")
return body.get("data")
The response envelope has traceId, code, msg and data. Code 0 means success. On an error, data is null, so read msg, keep the traceId, and do not try to parse data as if the call worked.
Keep the timeout explicit. A default requests call can hang far longer than your scheduler expects, and a hung worker looks exactly like a rate-limit problem until you check the logs.
Step 3: Add key-mode signing when you use an appId and apiKey
Token mode is one header: Authorization: Bearer YOUR_API_TOKEN. Key mode adds appId, ts, nonce and sign. The sign is SHA-256 over appId + traceId + ts + nonce + apiKey, in that exact order, converted to uppercase hex. ts is a Unix timestamp in milliseconds; nonce is a short random string.
import hashlib
import secrets
import time
import uuid
def key_headers(app_id, api_key):
trace_id = str(uuid.uuid4())
ts = str(int(time.time() * 1000))
nonce = secrets.token_hex(8)
raw = f"{app_id}{trace_id}{ts}{nonce}{api_key}"
sign = hashlib.sha256(raw.encode("utf-8")).hexdigest().upper()
return {
"Content-Type": "application/json",
"traceId": trace_id,
"appId": app_id,
"ts": ts,
"nonce": nonce,
"sign": sign,
}
Do not sort, lowercase or URL-encode the concatenation. Do not reuse a nonce or a timestamp across requests. If the signature is rejected, check the order first, then the uppercase hex, then whether ts is in milliseconds rather than seconds — that single digit error accounts for more failed handshakes than anything else.
Step 4: Plan your request budget before you hit the limit
The ceiling is 200 requests per minute and 24,000 per hour for each API, and going over may lock that API for two hours. A local budget with headroom keeps you under both. The class below pauses when the last minute holds 190 calls or the last hour holds 23,000, which leaves space for other processes using the same credentials.
import time
from collections import deque
class RequestBudget:
def __init__(self, per_minute=190, per_hour=23000):
self.per_minute = per_minute
self.per_hour = per_hour
self.minute = deque()
self.hour = deque()
def wait(self):
while True:
now = time.time()
while self.minute and now - self.minute[0] >= 60:
self.minute.popleft()
while self.hour and now - self.hour[0] >= 3600:
self.hour.popleft()
if len(self.minute) >= self.per_minute:
time.sleep(max(0.5, 60 - (now - self.minute[0])))
continue
if len(self.hour) >= self.per_hour:
time.sleep(max(0.5, 3600 - (now - self.hour[0])))
continue
stamp = time.time()
self.minute.append(stamp)
self.hour.append(stamp)
return
Pair the budget with a strict response check: raise on any code other than 0, print the traceId, and stop. Business errors do not improve on retry, because a failed validation fails again in 200 milliseconds. Only transport errors deserve another attempt, and those should use exponential back-off capped at a handful of tries.
If you do trigger the lockout, wait it out. A retry storm keeps the same API saturated and gives you no new information; a slow queue that drains after the window is the cheaper outcome. Batching also helps: 500 profile operations sent one at a time spend 500 requests, while a single call that accepts a list spends one.
Step 5: Call cloud phone lifecycle endpoints
Cloud phone endpoints cover create, start, stop, clone, delete, status query, GPS get and set, network settings, screenshots and device ID. Start with a read-only call so you learn the response shape before anything changes state:
PHONE_LIST = "/openapi/v1/phone/list" # confirm the exact path in the Markdown docs
result = post(PHONE_LIST, {"page": 1, "pageSize": 20})
print(json.dumps(result, indent=2))
The path above is a placeholder. Confirm the exact string for your API version in the Markdown docs before you ship it, because a missing or renamed path is the most common reason a call that works in the dashboard returns a 404 from code. A create call needs the phone or plan settings from the docs; a start call needs the phone identifier. Response data is endpoint-specific, so print it once and map only the fields you need into your own model.
Long operations should not sit in a tight poll loop. Fire the call, let a webhook tell you it finished, and poll a status endpoint only as a slow fallback. That keeps your request budget for work that actually needs a response.
Automation tasks and what they can touch
The API layer also exposes automation tasks, and the community TypeScript SDK ships platform modules for TikTok, Instagram, Facebook and YouTube. Treat those as workflow triggers for accounts you are authorised to manage, inside each platform’s own terms. Programmatic access does not create permission you did not already have, and it does not reset a limit you have hit.
Step 6: Keep the browser local API on a separate base URL
Browser profile calls are not the same target as cloud phone calls. The public API base URL is https://openapi.geelark.com. The browser local client API defaults to http://localhost:40185/api/v1 and can be overridden with GEELARK_BROWSER_BASE_URL. Send a browser call to the public host and it fails even when the token is valid.
import os
BROWSER_BASE_URL = os.getenv(
"GEELARK_BROWSER_BASE_URL",
"http://localhost:40185/api/v1",
).rstrip("/")
def browser_url(path):
if not BROWSER_BASE_URL.startswith(("http://localhost", "http://127.0.0.1")):
raise ValueError("Point GEELARK_BROWSER_BASE_URL at the local GeeLark client")
return f"{BROWSER_BASE_URL}/{path.lstrip('/')}"
print(browser_url("/browser/list")) # path still comes from the Markdown docs
Browser endpoints cover create, edit, delete, launch, close, list, status check, cache clear, clone, kernel update, bookmarks and environment cookies. Launch returns a debug port. That port is a local control surface for the running profile, so bind it to localhost and never expose it to the public internet or to a shared CI runner. For a broader walkthrough of profile operations, this browser profile management API tutorial covers the surrounding patterns.
If your work is browser profiles for authorised accounts rather than cloud phones, Send.win takes a different approach. Each profile keeps its own cookies, storage, sessions, fingerprint settings and proxy, available as a desktop app for Windows, macOS and Linux and as a cloud browser. It has no API, SDK or automation interface, so it complements a coding workflow rather than replacing GeeLark inside one.
Step 7: Receive operation results with a webhook receiver
Polling every operation burns the budget you just built. Webhooks send operation results and event notifications to a URL you configure. Run a small receiver, put it behind HTTPS, and log the payload before you act on it.
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.post("/geelark/webhook")
def geelark_webhook():
event = request.get_json(silent=True) or {}
print("GeeLark event:", event)
return jsonify({"code": 0, "msg": "ok"}), 200
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
Do not add a signature check unless the docs for your webhook state that one is sent. Keep the handler idempotent, because the same event may arrive more than once and a duplicate start call is worse than a missed notification. If you are wiring several services behind one endpoint, the same pattern applies when you integrate cloud browser APIs into a workflow: verify, log, then act.
Step 8: Connect the MCP server only after the raw client works
GeeLark also ships an MCP server for AI clients such as Codex, Claude, Cursor and Trae. The command is npx -y geelark-cli@latest mcp, or geelark-cli mcp if the CLI is already installed. You need a GEELARK_TOKEN from GeeHub and a paid plan with API access.
import json
config = {
"mcpServers": {
"geelark": {
"command": "npx",
"args": ["-y", "geelark-cli@latest", "mcp"],
"env": {
"GEELARK_TOKEN": "set-this-locally",
"GEELARK_BASE_URL": "https://openapi.geelark.com",
"GEELARK_BROWSER_BASE_URL": "http://localhost:40185/api/v1",
},
}
}
}
print(json.dumps(config, indent=2))
The token is entered locally; do not paste it into a chat window and do not commit a shared .mcp.json that contains it. GEELARK_BASE_URL and GEELARK_BROWSER_BASE_URL override the default addresses, so keep the browser value on localhost when the local client is in play. Debug the raw HTTP client first: when MCP returns something odd, you want to know already whether the endpoint or the agent is at fault.
When a typed client saves you time
Two options sit between hand-written requests and a full integration. The official repository’s OpenAPI YAML can generate types, but only for the endpoints it covers, so generated clients break quietly the moment you call something documented only in Markdown. The community geelark-sdk npm package wraps 28 modules and 145+ methods with automatic signing, retries with exponential back-off and typed responses, and needs nothing beyond Node’s crypto and fetch.
Both are worth reading for the signing details even if you write Python. Neither is maintained by GeeLark — that SDK and the community MCP server are unaffiliated projects — so pin a version, read the diff before upgrading, and keep one integration test pointed at a read-only endpoint so a breaking change surfaces in CI rather than in production.
Common errors and how to fix them
Most failures fall into a small set. Read code and msg before you change anything, and put the traceId in every log line.
| Error | What it usually means | Fix |
|---|---|---|
| HTTP 401 or auth error | Token missing, or key mode selected without appId and apiKey. | Check GEELARK_AUTH_MODE. Token mode sends Authorization: Bearer; key mode sends appId, ts, nonce and sign. |
| sign invalid | Wrong concatenation order, lowercase hex, or seconds instead of milliseconds. | Use appId + traceId + ts + nonce + apiKey, SHA-256, uppercase, with ts in milliseconds. |
| code not 0, data null | The request reached GeeLark but failed validation, permission or state checks. | Read msg, log the traceId, fix the body or the plan access. Do not retry blind. |
| Rate limit or two-hour lockout | More than 200 requests in a minute or 24,000 in an hour. | Add the request budget, wait out the window, and stop parallel loops against the same API. |
| 404 on a documented endpoint | Path missing from openapi.yaml, or the wrong API version. |
Use the Markdown docs as the source of truth and keep paths in constants. |
| Browser call fails on the public host | The browser local API uses a different base URL. | Set GEELARK_BROWSER_BASE_URL to http://localhost:40185/api/v1 unless your install uses another port. |
| Webhook never arrives | URL is not public, HTTPS is missing, or the receiver is behind a firewall. | Test the route locally, expose it through your reverse proxy, then check the webhook configuration. |
| MCP works in one client but not another | Config file in the wrong directory, or the token was not loaded. | Check the client’s MCP config path, run the CLI once by hand, and keep secrets in local env only. |
If an account is restricted, use the platform’s own appeal or verification process. The API is for managing cloud phones and profiles you own or are authorised to manage, not for working around a platform limit or a suspension.
🏆 Send.win Verdict
GeeLark is the right fit when you need code-level control over cloud phones and browser profiles. If your work is browser-based and you manage accounts you own or are authorised to manage, Send.win is a browser profile manager with isolated profiles, per-profile fingerprint protection, residential proxies and team sharing, available as a desktop app for Windows, macOS and Linux plus a cloud browser running in Helsinki. It has no API, SDK or automation interface, so it will not replace GeeLark in a programmatic workflow — and it does not pretend to.
See Send.win plans — plans from $19/mo with a 7-day money-back guarantee on your first card purchase, or try the free cloud demo first.
Frequently Asked Questions
How do I authenticate a GeeLark API request?
Send a POST with a JSON body and a traceId header containing a Version 4 UUID. In token mode add Authorization: Bearer YOUR_API_TOKEN. In key mode add appId, ts, nonce and sign, where the sign is SHA-256 over appId + traceId + ts + nonce + apiKey, uppercased.
What is the sign formula for GeeLark key-mode requests?
SHA256(appId + traceId + ts + nonce + apiKey) in that exact order, converted to uppercase hex. ts is Unix milliseconds and nonce is a short random string. Do not reuse either value across requests, and do not URL-encode the concatenation.
What is the GeeLark API rate limit?
200 requests per minute and 24,000 per hour per API. Exceeding either may lock the API for two hours. Build a request budget with headroom and avoid parallel retry loops against the same endpoint.
Why does my GeeLark API call return a code other than 0?
The request reached GeeLark but failed validation, permission or state checks. Read msg, log the traceId, and check the body and your plan access. data is null on error, so do not parse it as a success payload.
Which GeeLark plan includes API access?
Not Free. API access starts at the Base plan and is included on Pro and Custom, and the MCP setup also requires a paid plan with API access. Check the current caps for profiles and daily open/create actions before you size a fleet.
Can I manage browser profiles through the GeeLark API?
Yes. Browser endpoints cover create, edit, delete, launch, close, list, status, cache clear, clone, kernel update, bookmarks and environment cookies. Use https://openapi.geelark.com for public API calls and http://localhost:40185/api/v1 for the local browser client unless your install overrides the port.
How do I receive GeeLark webhook callbacks?
Configure a URL in GeeLark for operation results and event notifications. Run an HTTPS receiver, log the payload, and make the handler idempotent. Do not add a signature check unless the webhook docs say a signature is sent.
Does GeeLark have an MCP server for Claude or Cursor?
Yes. Run npx -y geelark-cli@latest mcp with a GEELARK_TOKEN from GeeHub, or geelark-cli mcp if the CLI is installed. Keep the token in local config, never in a committed shared file, and remember the setup needs a paid plan with API access.
How Send.win Helps With Geelark Api
Send.win is a browser profile manager for professionals and teams: every account gets its own isolated browser profile.
- Isolated profiles – separate cookies, storage and sessions per profile
- Fingerprint protection – each profile keeps its own consistent browser fingerprint, so profiles never share one browser identity
- Desktop app + cloud browser – native app for Windows, macOS and Linux, or run profiles in the cloud with no install
- Residential proxies included – on Pro and Team, with timezone and locale matched to the proxy
- Team sharing – share a signed-in profile with a teammate without sharing the password
Try the free cloud demo — no install — or download the desktop app. Paid plans start at $19/month, and your first card purchase is covered by a 7-day money-back guarantee (see pricing).