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:

OperationPurposeOps note
Create profile / envNew device fingerprint rowOne env per TikTok/IG account in prod
Set proxyGeo-aligned mobile IPMatch geo in CMDB to proxy country
Start + ADB infoRemote shell accessRetry start if ADB port not ready in 60s
Batch tagsGroup by client or campaignFilter 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.

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

  1. adb devices shows device as device, not offline
  2. adb shell getprop ro.product.model matches expected SKU
  3. Proxy egress IP country matches CMDB geo (curl ifconfig.me via device browser or proxy check API)
  4. 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)

CriteriaGeeLarkDuoPlus
ADB stability under loadStrong on paid tiers; tag batching helpsGood; watch cold-start latency
Proxy injectionDashboard + APIAPI + template profiles
Team / sub-accountWorkspace separationPlan-dependent
When to add MultiloginMeta 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.