DocsAPI

.md

Controlling a phone

All pages

Driving a phone yourself takes two calls in a loop: observe to see it, and act to do one thing. Each call holds the phone for your key for five minutes, so a run or another key cannot take it from under you.

Endpoints

POST
What is on the phone now: a screenshot, the app in front, and if you ask, the elements on screen.
POST
/v1/phones/{id}/actcontrol:write
Does one action, and can observe the result in the same call.
POST
Holds the phone for your key, for 30 to 900 seconds (300 by default). Renews a lease you already hold.
DELETE
Lets the phone go before the lease runs out.
POST
Starts putting a file of up to 100 MB on the phone, and says where to send its parts.
POST
Finishes an upload in parts: the file is checked and put on the phone. If it doesn't reach the phone, nothing of it is left there, and the error's reason says why: push-timeout, checksum-mismatch or upload-gone.
DELETE
Drops an upload you won't finish, and what was sent of it.

A phone held by a run or another key answers 409 leased. If someone takes over the phone in the console's live view, your commands fail with preempted until they let go.

Observe

screenshotobject
Include one. maxEdge is 240 to 2000 pixels (960 by default), format is jpeg or png, and quality is 30 to 95.
uiTreeboolean
Include the elements on screen, up to 400. Off by default, because apps can notice it being read.
foregroundboolean
Include the app and activity in front, and whether the keyboard is up. On by default.
settleobject
Wait for the screen to stop changing first: up to maxMs (3,000 by default) for quietMs of stillness (500 by default).

The screenshot comes back as base64 with its width and height. Send coordinates to act in that picture's pixels with its size as frame, and the phone scales them to its screen. Without a frame, they are the screen's own pixels.

Act

An action is an object with a type and the fields for it. Add then, which takes the same fields as observe, to see the result without a second call.

terminal
curl https://api.distilled.cx/v1/phones/phone-1/act \
  -H "Authorization: Bearer $DISTILLED_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: tap-search-0001" \
  -d '{
    "action": { "type": "tap", "x": 216, "y": 212 },
    "frame": { "width": 432, "height": 960 },
    "then": { "screenshot": {} }
  }'
typeFieldsDoes
tapx, yTaps a point
longPressx, y, durationMsHolds for 300 to 5,000 ms
swipex1, y1, x2, y2, durationMsSwipes over 80 to 5,000 ms
typetextTypes up to 500 characters of plain ASCII
keykeyPresses back, home, recents, enter, backspace, tab, escape, space, a direction or volume
launchApppackageOpens an app by its package name
openUrlurlOpens an http or https link in Chrome
waitmsWaits up to 10 seconds

Send an Idempotency-Key header with an action you might retry, and a repeat of the same request is not played twice.

Files

Put a video, a picture or any other file on a phone. A picture or video goes to the phone's camera roll, so its gallery and every app's media picker show it, and anything else goes to Downloads. The answer comes once the phone has the file, and says where it is and whether the gallery lists it. Unlike observe and act, this holds no lease, so it works while someone else drives the phone.

FieldMeaning
nameThe file's name on the phone: letters, digits, dots, dashes and underscores, with its extension
sha256Optional: what the bytes must hash to, if you want that checked
pathIn the answer: where the file is on the phone
galleryIn the answer: whether the camera roll lists it

The phone takes JPEG, PNG, WebP, GIF, HEIC, MP4, MOV, WebM, MKV, MP3, M4A, WAV, OGG, PDF, text, CSV, JSON and ZIP files, up to 100 MB each. Start an upload, send the file in parts of 16 MB to the address the answer names, each with its token, and finish the upload with every part's number and etag. A part that fails can simply be sent again. The parts go straight to storage, not through this API.

terminal
# Start. The answer names where to send the parts (uploadUrl), the token to send them with, and partBytes.
curl https://api.distilled.cx/v1/phones/phone-1/file-uploads \
  -H "Authorization: Bearer $DISTILLED_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "long.mp4", "size": 80000000 }'

# Cut the file into parts of partBytes (16 MB) and send each straight to the file store, numbered from 1.
# Each answers { "part": 1, "etag": "..." }.
split -b 16777216 -d long.mp4 part-
n=0; for p in part-*; do n=$((n+1))
  curl -X PUT "$UPLOAD_URL/parts/$n" \
    -H "Authorization: Bearer $UPLOAD_TOKEN" \
    --data-binary @"$p"
done

# Finish with every part's number and etag. The file is on the phone when this answers.
curl https://api.distilled.cx/v1/phones/phone-1/file-uploads/pfu_0123456789abcdef/finish \
  -H "Authorization: Bearer $DISTILLED_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "parts": [{ "part": 1, "etag": "a1b2c3" }] }'
  • An upload stays open until expiresAt, an hour plus two minutes for each part, and its token is never renewed.
  • A workspace can have 5 open at once; a sixth answers 429 busy until one is finished or dropped. It can start 20 GB of uploads a day (UTC); past that a start answers 429 quota-exceeded.
  • Every part is partBytes long except the last. A part that failed can be sent again with the same token, in any order, and the last copy is kept. Once you finish, the upload is closed whatever the answer.
  • The upload's token lets in parts of that one upload and nothing else, so a web page on any site can send the parts from the browser without your API key: start and finish the upload from your server, and hand the page only uploadUrl and token.
A part answersMeaning
200Kept. The body has the part's part and etag.
400The part is past the file's last, or isn't the length that part must be. Nothing was kept.
401The token is wrong or the upload ran out of time. Start again.
409The upload was finished or dropped.
411The part was sent without a length.
413The part is longer than partBytes.

Something missing or unclear? Tell us and we will fix the page. The API also describes itself in OpenAPI.