← back to blog

cloud phone API basics: getting started in 2026

May 07, 2026

cloud phone API basics: getting started in 2026

cloud phone API basics in 2026 are simpler than most people expect. once you understand four endpoints (auth, list, lock, unlock) you can build almost anything: a CI test runner, a multi-account farm controller, a customer support tool, an automation that takes a screenshot every hour. this guide walks you through the entire surface in plain language, with copy-pasteable code, so by the end of the page you have a working API call against a real Singapore Android device.

if you have not seen any of the broader integration patterns yet, the CI/CD pipeline guide shows what these primitives compose into. this article focuses on the smallest useful unit: one user, one device, one script.

the conceptual model

every cloud phone API has the same five concepts.

most API calls either inspect the state of one of these or change it. that is the whole model.

getting your API key

log into the cloudf.one dashboard, go to settings, then API keys. click create, name the key (e.g. “local dev”), and copy the secret. it shows once. paste it into your env file.

export CLOUDFONE_TOKEN="cf_live_..."
export CLOUDFONE_API="https://api.cloudf.one/v1"

never commit this to git. for production systems, use a secret manager like AWS Secrets Manager, GCP Secret Manager, or HashiCorp Vault.

your first call: list devices

the simplest authenticated request is GET /devices. it returns the devices your account can see, with state, region, and tags.

curl -s -H "Authorization: Bearer $CLOUDFONE_TOKEN" \
  "$CLOUDFONE_API/devices" | jq .

response looks like this.

{
  "devices": [
    {
      "id": "dev_4f2a",
      "model": "Samsung S22",
      "region": "sg",
      "state": "available",
      "tags": ["pool-default"]
    }
  ]
}

if you get a 401, double-check the bearer token. if you get a 403, your key probably lacks the read scope. recreate it with full access for now and tighten scopes once your code works.

locking a device

before you can use a device for testing or browsing, lock it. locking gives you exclusive access for a duration you specify, and it returns an ADB endpoint you can connect to.

import os, requests

API = os.environ["CLOUDFONE_API"]
TOKEN = os.environ["CLOUDFONE_TOKEN"]
H = {"Authorization": f"Bearer {TOKEN}"}

r = requests.post(f"{API}/devices/lock", headers=H, json={
    "tag": "pool-default",
    "duration": 600
}).json()

device_id = r["device_id"]
adb_endpoint = r["adb_endpoint"]
print(device_id, adb_endpoint)

duration is in seconds. 600 means 10 minutes. always set this to slightly less than your job timeout so your script unlocks before the lock auto-expires.

connecting via ADB

once you have the endpoint, plain adb works.

adb connect $ADB_ENDPOINT
adb -s $ADB_ENDPOINT shell getprop ro.product.model
adb -s $ADB_ENDPOINT install -r app-debug.apk

if you have never used ADB over network, the Android developer docs on adb-over-wifi cover the basics. the only difference here is that the device is in Singapore instead of on your desk.

taking a screenshot via API

you do not always need ADB. for one-shot tasks like “take a screenshot every hour”, the REST API is faster.

r = requests.post(f"{API}/devices/{device_id}/screenshot", headers=H).json()
print(r["url"])  # signed URL, valid 15 minutes

drop the URL into Slack, Discord, Notion, anywhere. the Slack notifications guide shows the full pattern.

unlocking when you are done

always call unlock. always.

requests.post(f"{API}/devices/{device_id}/unlock", headers=H)

put it in a finally block, or in your CI’s after_script equivalent. devices left locked cost money and block the next user.

try:
    # ... your work ...
finally:
    requests.post(f"{API}/devices/{device_id}/unlock", headers=H)

handling errors gracefully

four error codes you will see most often.

a simple retry wrapper covers the transient cases.

import time

def with_retry(fn, attempts=3, delay=2):
    for i in range(attempts):
        try:
            return fn()
        except requests.HTTPError as e:
            if e.response.status_code in (409, 503) and i < attempts - 1:
                time.sleep(delay * (2 ** i))
                continue
            raise

rate limits and pagination

cloudf.one rate limits at 60 requests per minute per token by default. heavy users get higher tiers. the X-RateLimit-Remaining header tells you where you stand. when it hits zero, the API returns 429 and a Retry-After header.

for endpoints that return lists (/devices, /sessions, /events), pagination is cursor-based.

url = f"{API}/devices?limit=50"
while url:
    page = requests.get(url, headers=H).json()
    for d in page["devices"]:
        process(d)
    url = page.get("next_url")

what to build next

once you have lock, unlock, and ADB working, you can build:

start with one of these. ship it small. then layer in the harder pieces (parallel jobs, RBAC, billing controls).

frequently asked questions

do I need any SDK or can I just use curl?

curl works for everything. cloudf.one publishes a Python SDK and a Node SDK for convenience but neither is required. anything that can speak HTTPS speaks the API.

what is the difference between locking by ID and locking by tag?

locking by ID gives you a specific device. locking by tag asks the platform to pick any available device matching that tag. tags are how you build pools.

how long can I hold a lock?

the default max is 24 hours. higher limits are available on enterprise plans. for normal CI testing, 10-30 minutes is right.

can I run multiple parallel calls with one API key?

yes, up to your rate limit. for high-throughput automation (more than 60 RPM) request a higher tier or rotate across multiple keys.

does the API support iOS devices?

cloudf.one is Android-only. iOS support is on the 2027 roadmap. for now you would need a separate provider for iOS.

ready to fire your first API call against a real Singapore phone? grab a cloudf.one trial token and run the code above as is.