Cloud phone vendors differ in UI polish, but production teams care about the same API contract: create environment → bind proxy → start → get ADB endpoint → run job → stop → record health. This guide maps that flow for GeeLark and DuoPlus — illustrative endpoints; always verify against vendor docs.
Shared lifecycle model
POST /env/create → env_id
POST /env/{id}/proxy → bind mobile or residential endpoint
POST /env/{id}/start → status=running, adb_host, adb_port
adb connect host:port
run Appium / uiautomator / manual warm-up
POST /env/{id}/stop → release billing minutes
PATCH /cmdb/{account} → ban_status, last_job_at
GeeLark Open API pattern
GeeLark teams typically automate via Open API with bearer token from the dashboard. Common operations:
| Operation | Purpose | Ops note |
|---|---|---|
| Create profile / env | New device fingerprint row | One env per TikTok/IG account in prod |
| Set proxy | Geo-aligned mobile IP | Match geo in CMDB to proxy country |
| Start + ADB info | Remote shell access | Retry start if ADB port not ready in 60s |
| Batch tags | Group by client or campaign | Filter API list by tag for worker routing |
DuoPlus API pattern
DuoPlus exposes REST-style device management. Same mental model — differences are usually auth header names, proxy format (SOCKS5 vs HTTP), and whether ADB is returned inline or via a second poll.
- Poll start status — some plans return
pendingfor 30–90s before ADB is live - Device SKU — map API
device_typeto your warm vs prod tier - Concurrent limit — respect plan cap; queue in worker, do not fire unbounded creates
Python asyncio device pool
import asyncio
import httpx
VENDOR = "geelark" # or "duoplus"
BASE = "https://api.example-vendor.com" # verify official base URL
TOKEN = "YOUR_API_TOKEN"
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
async def start_env(client: httpx.AsyncClient, env_id: str) -> dict:
r = await client.post(f"{BASE}/env/{env_id}/start", headers=HEADERS, timeout=120)
r.raise_for_status()
data = r.json()
adb = f"{data['adb_host']}:{data['adb_port']}"
# subprocess: adb connect adb (see ADB guide)
return {"env_id": env_id, "adb": adb}
async def with_device(env_id: str, fn):
async with httpx.AsyncClient() as client:
info = await start_env(client, env_id)
try:
await fn(info)
finally:
await client.post(f"{BASE}/env/{env_id}/stop", headers=HEADERS)
async def worker(env_id: str, sem: asyncio.Semaphore):
async with sem:
await with_device(env_id, run_warmup_job) # your ADB/Appium logic
async def main():
ids = ["env-101", "env-102"] # from CMDB
sem = asyncio.Semaphore(3) # below vendor concurrent cap
await asyncio.gather(*(worker(i, sem) for i in ids))
Health gates before automation
adb devicesshows device asdevice, notofflineadb shell getprop ro.product.modelmatches expected SKU- Proxy egress IP country matches CMDB
geo(curl ifconfig.me via device browser or proxy check API) - Google Play Integrity / app-specific checks — see troubleshooting
Pair with desktop anti-detect
Mobile API jobs should not share sessions with Multilogin web profiles. Use a CMDB row per account — see hybrid architecture. Web ads and seller dashboards stay on Multilogin; native apps stay on cloud phone.
Vendor comparison (API ops lens)
| Criteria | GeeLark | DuoPlus |
|---|---|---|
| ADB stability under load | Strong on paid tiers; tag batching helps | Good; watch cold-start latency |
| Proxy injection | Dashboard + API | API + template profiles |
| Team / sub-account | Workspace separation | Plan-dependent |
| When to add Multilogin | Meta BM, web TikTok Shop, any desktop-only flow | |
Related
Disclosure: MLX-MMO affiliated with Multilogin. Vendor API paths are illustrative — confirm with GeeLark and DuoPlus official documentation.