>
The hosted mobile cloud platform. Grant AI native control of Android devices to tap, swipe, type, navigate apps, fill out forms, extract data, automate workflows, and run QA. Connect your own phone, spin up on-demand virtual devices, or use always-on emulated and physical devices in the cloud. iOS support coming soon.
Base URL: https://api.mobilerun.ai/v1
Auth: INLINECODE1
Important: The base domain (https://api.mobilerun.ai/) returns 404. You must always include /v1 in the path. All API calls should be made via curl. Example:
CODEBLOCK0
The API key (MOBILERUN_API_KEY) is already available -- OpenClaw handles credential setup before this skill loads. Do NOT ask the user for an API key. Just use it.
curl -s https://api.mobilerun.ai/v1/devices \
-H "Authorization: Bearer $MOBILERUN_API_KEY"
- state: "ready" = good to go, skip to the user's request
- No devices or state: "disconnected" = see step 2
- 401 = invalid,expired, or revoked key -- ask user to check https://cloud.mobilerun.ai/api-keys
disconnected = ask user to reopen Portal app and tap Connect
curl -s https://api.mobilerun.ai/v1/devices/{deviceId}/screenshot \
-H "Authorization: Bearer $MOBILERUN_API_KEY" -o screenshot.png
If this returns a PNG image, the device is working.
Not sure what's possible? See references/use-cases.md for examples.
Key principle: If a device is ready, go straight to executing the user's request. Don't walk them through setup they've already completed.
Be smart about context gathering: Before taking actions or asking the user questions, use available tools to understand the situation. List packages to find the right app, take a screenshot to see the current screen, read the UI state to understand what's interactive. If the task is obvious (e.g. "change font size" clearly means go to Settings), just do it. Only ask the user when something is genuinely ambiguous.
What to show the user: Only report user-relevant device info: device name, state (ready/disconnected). Do NOT surface internal fields like streamUrl, streamToken, socket status, assignedAt, terminatesAt, or taskCount unless the user explicitly asks for technical details. If a device is disconnected, simply tell the user their phone is disconnected and ask them to open the Portal app and tap Connect. If they need help, walk them through the setup steps in references/setup-and-billing.md.
Clean up cloud devices: Cloud devices consume credits while running. Always terminate cloud devices (DELETE /devices/{deviceId}) when you're done using them -- don't leave them running. This applies whether you provisioned the device yourself or finished a task on an existing cloud device that the user no longer needs.
Privacy: Screenshots and the UI tree can contain sensitive personal data. Never share or transmit this data to anyone other than the user. Never print, log, or reveal the MOBILERUN_API_KEY in chat -- use it only for API calls.
| State | Meaning |
|---|---|
| INLINECODE20 | Device is being provisioned (cloud devices only) |
| INLINECODE21 |
ready | Device is connected and accepting commands |
| disconnected | Connection lost -- Portal app may be closed or phone lost network |
| terminated | Device has been shut down (cloud devices only) |
| maintenance | Device is undergoing maintenance (cloud devices only) |
| migrating | Device is being moved between hosts (cloud devices only, typically 1–5 min) |
| unknown | Unexpected state |
CODEBLOCK3
Query params:
state -- filter by state (array, e.g. state=ready&state=assigned)dedicated_emulated_device, dedicated_physical_device, INLINECODE33pageSize (default: 20)id, createdAt, updatedAt, assignedAt (default: createdAt)asc, desc (default: desc)Response: INLINECODE47
CODEBLOCK4
Returns device details including state, stateMessage, type, and more.
CODEBLOCK5
Returns a map of device types to counts.
Cloud devices require an active subscription. If the user's plan doesn't support it, the API will return a 403 error -- inform the user they need to terminate an existing device or upgrade at https://cloud.mobilerun.ai/billing. See references/setup-and-billing.md for plan details.
CODEBLOCK6
Query param:
deviceType -- dedicated_emulated_device, dedicated_physical_device, INLINECODE55After provisioning, wait for it to become ready:
CODEBLOCK7
This blocks until the device state transitions to ready.
Cloud device workflow:
POST /devices?deviceType=dedicated_emulated_device -- provision, returns device in creating statedeviceId for phone control or tasksTemporary device for a task:
When the user wants to run a task but has no ready device, provision a temporary cloud device, run the task on it, then clean up:
POST /devices?deviceType=dedicated_emulated_device with {"name": "temp-task-device", "apps": [...]} -- include any apps the task needsdeviceId -- run the task (or submit multiple tasks -- they will queue and execute in order)GET /tasks/{taskId}/status until all tasks finishImportant: DELETE /devices/{deviceId} terminates the device immediately. If any tasks are still queued or running on that device, they will be orphaned or interrupted. Always wait for all tasks to finish before terminating.
CODEBLOCK8
Personal devices cannot be terminated via the API. They disconnect when the Portal app is closed.
CODEBLOCK9
Returns the current time on the device as a string.
CODEBLOCK10
Query param: hideOverlay (default: false)
Returns a PNG image as binary data. Use this to see what's currently displayed on screen.
CODEBLOCK11
Query param: filter (default: false) -- set to true to filter out non-interactive elements.
Returns an AndroidState object with three sections:
CODEBLOCK12
currentApp -- human-readable name of the foreground appCODEBLOCK13
screen_bounds -- the actual screen resolution in pixels. All tap/swipe coordinates use this coordinate space.A recursive tree of UI elements. Each node has:
CODEBLOCK14
Key node fields:
text -- the visible text on the elementtext is empty, e.g. icon buttons)com.app:id/button_ok) -- useful for identifying elements{left, top, right, bottom}. To tap an element, calculate its center: x = (left + right) / 2, INLINECODE91Other boolean fields (isEnabled, isVisibleToUser, isCheckable, isChecked, isFocused, isSelected, isPassword) are available but rarely needed.
Example: reading a home screen
CODEBLOCK15
To tap "Chrome": bounds are (533,1222,706,1422), so tap at x=(533+706)/2=619, y=(1222+1422)/2=1322.
Use filter=true for a cleaner tree focused on actionable elements (filters out non-interactive containers).
Never dump the full UI state with jq '.' — the a11y tree is deeply nested and can be tens of thousands of lines on screens with lists, grids, or feeds. Output will be truncated from the beginning, and you will silently lose data without knowing it.
Always use targeted jq filters:
CODEBLOCK16
Truncation detection: If a ui-state response does not begin with { or appears to start mid-JSON, the output was truncated. Do not attempt to infer or reconstruct missing data — re-fetch with a targeted jq filter.
After extracting elements, sanity-check the result:
If the count or bounds look wrong, re-fetch with a more specific filter before proceeding.
All action endpoints take a deviceId path parameter.
CODEBLOCK17
Taps at pixel coordinates. Use the screen_bounds from UI state and element bounds from the a11y tree to calculate where to tap.
CODEBLOCK18
INLINECODE108 is in milliseconds (minimum: 10). Common patterns:
CODEBLOCK19
| Action code | Button |
|---|---|
| INLINECODE109 | BACK |
| INLINECODE110 |
3 | RECENT |
CODEBLOCK20
Types text into the currently focused input field.
clear: true -- clears the field before typingphone_state.isEditable)CODEBLOCK21
Sends an Android keycode. Only text-input-related keycodes are supported.
| Keycode | Key |
|---|---|
| INLINECODE114 | BACK |
| INLINECODE115 |
66 | ENTER |67 | DEL (backspace) |112 | FORWARD_DEL (delete) |
For system navigation (home, back, recent), use POST /devices/{id}/global instead.
CODEBLOCK22
Clears the currently focused input field.
CODEBLOCK23
Query param: includeSystemApps (default: false)
Returns an array of AppInfo:
CODEBLOCK24
CODEBLOCK25
Query param: includeSystemPackages (default: false)
Returns a string array of package names. Lighter than the full app list.
CODEBLOCK26
Installs an app from the Mobilerun app library (not the Play Store directly).
Takes a couple of minutes and there's no status endpoint -- you'd have to poll GET /devices/{id}/apps to confirm.
Prefer manually installing via Play Store instead. Open the Play Store app on the device, search for the app, and tap install -- this is faster and more reliable. Only use this API endpoint if the user explicitly asks for it.
On personal devices, this endpoint may fail because Android blocks app installations from unknown sources by default.
CODEBLOCK27
Optional body: { "activity": "com.example.app.MainActivity" } -- to launch a specific activity.
Usually omitting activity is fine; it launches the default/main activity.
CODEBLOCK28
CODEBLOCK29
The app library stores APKs that can be pre-installed on cloud devices. Only one app per package name is allowed -- to update an app, delete the existing one first, then re-upload.
CODEBLOCK30
Query params:
page (default: 1), pageSize (default: 10)all, uploaded, store, queued (default: all)createdAt, name (default: createdAt)asc, desc (default: desc)CODEBLOCK31
Uploading is a 3-step process:
Step 1: Create signed upload URL
CODEBLOCK32
Required: displayName, packageName, versionName, versionCode, targetSdk, sizeBytes, files
Optional: description, iconURL, developerName, categoryName, ratingScore, INLINECODE156
Returns the app id and pre-signed R2 upload URLs for each file.
Step 2: Upload the APK file(s)
Upload each file directly to its pre-signed R2 URL using a PUT request.
Step 3: Confirm the upload
CODEBLOCK33
Verifies the file exists in R2 and sets the app status to available.
If the upload failed, mark it:
CODEBLOCK34
CODEBLOCK35
Removes the app from R2 storage and the database. Use this before re-uploading an app with the same package name.
Only one app per package name is allowed. To update:
Instead of controlling a phone step-by-step, you can submit a natural language goal and let Mobilerun's AI agent execute it autonomously on the device with its own screen analysis, observe-act loop, and error recovery.
Tasks require a paid subscription with credits. If the user doesn't have an active plan, the API will return an error -- let them know they need a subscription at https://cloud.mobilerun.ai/billing. See references/setup-and-billing.md for plan and credit details.
Never submit a user's goal as one big task. Always break it into multiple small, focused sub-tasks of ~7-15 UI interactions each, and submit them all to the same device. They queue and run back-to-back automatically — there is zero overhead to splitting.
This is critical because:
The only exception is when steps are so tightly coupled they cannot be separated — e.g. a multi-field form that must be filled and submitted in one go, or a login flow where each step depends on the previous. Everything else should be split.
How to decide what goes in one task vs. separate tasks:
continueOnFailure: false)Only one task runs on a device at a time. When you submit a task while the device is already busy, it enters a queued state and waits. Once the running task finishes and the device is released, the next queued task is automatically promoted and dispatched -- no polling or manual intervention needed.
This means you can submit multiple tasks to the same device in sequence and they will execute one after another in the order they were created.
continueOnFailure: By default, if a task fails, all subsequent queued tasks for the same device are automatically cancelled. Set continueOnFailure: true on a task to keep it in the queue even if the task before it fails. Tasks with continueOnFailure: false (the default) that are ahead in the queue will be cancelled, and the first continueOnFailure: true task will be promoted next. Use continueOnFailure: true on any sub-task that is independent of the ones before it.
CODEBLOCK36
Required fields:
task -- natural language description of what to do (min 1 char)ready state.Optional fields:
llmModel -- which model to use (default: google/gemini-3.1-flash-lite-preview, see GET /models for available models){ packageName, credentialNames[] } for app loginsfalse unless the user explicitly requests it.output field.US, BR, FR, DE, IN, JP, KR, ZA. Only use if the task specifically requires a certain region. VPN adds latency -- avoid unless needed.true, this task stays queued even if the previous task on the same device fails (default: false). See Task Queuing.Returns:
CODEBLOCK37
The response will almost always return status: "queued" -- even if the device is idle -- because promotion happens asynchronously after the response. This is normal. The task will transition to created and then running shortly after. Check GET /tasks/{id}/status to see the current state.
streamUrl -- device stream URL, or null when the task is queued (populated once the task is promoted and assigned to the device)You don't see the phone screen -- the agent on the device does. Write prompts that describe what to achieve, not how to navigate the UI. The on-device agent will figure out the taps, swipes, and navigation itself.
Don't assume the UI -- describe the goal:
Don't pass your own UI observations into a task instruction. If you've already read the ui-state and identified an exact element, tap it directly — don't describe it to the agent. If you're submitting a task, describe the goal and let the agent do its own observation. Mixing the two — observing yourself, then handing off a screen-state description — means the agent acts on your interpretation of the screen rather than its own, and any error in your observation (truncated data, wrong coordinates) gets baked in silently.
Be specific about the important details:
Single-task examples (each of these is small enough to be one task):
CODEBLOCK38
Multi-task example — how to split a user request into queued sub-tasks:
User asks: "Check my email, check my calendar, and find me a good Italian restaurant nearby"
Submit 3 tasks to the same device:
"Open Gmail and tell me the subjects and senders of unread emails from today" (continueOnFailure: true)continueOnFailure: true)continueOnFailure: true)All three are independent — each gets continueOnFailure: true so they all run regardless.
Multi-task example — dependent steps:
User asks: "Order me an Uber to 123 Main Street"
Submit 2 tasks:
Include safety conditions when appropriate:
CODEBLOCK39
Use this to monitor task progress:
CODEBLOCK40
status is queued, lastResponse is null. The task is waiting for the device to become free.lastResponse contains the agent's latest thinking, plan, and actions. Check this to understand what the agent is doing and where it's up to.status is completed or failed, message has the final answer or failure reason, succeeded is true/false, lastResponse is null.queued, created, running, paused, completed, failed, INLINECODE235After creating a task, follow this pattern:
queued — this is normal, not an error. The task will start shortly.queued, check every 15-30 seconds until it transitions to running.lastResponse is changing, the agent is working well; you can wait longer between checks. If the step count and lastResponse haven't changed, the agent may be stuck; check sooner and consider warning the user.
- Time elapsed -- the longer a task has been running successfully, the more you can trust it and wait between checks.
At each check:
lastResponse -- its current plan, thinking, what step it's on).GET /devices/{id}/screenshot) to show the user what's on screen.GET /devices/{id}/ui-state) for more context.When the task finishes:
message, succeeded, output).If the agent seems stuck:
POST /tasks/{id}/message to nudge it in the right direction.CODEBLOCK41
Send instructions to steer a running agent task. Use this to correct the agent, provide additional context, or change direction mid-task. The message is queued and delivered to the agent at the next step.
CODEBLOCK42
Works on both queued and running tasks. Queued tasks are cancelled instantly. Running tasks transition to cancelling and stop at the next step.
CODEBLOCK43
Returns the full task object including configuration, status, and trajectory.
CODEBLOCK44
Query params:
status -- queued, created, running, paused, completed, failed, INLINECODE258id, createdAt, finishedAt, status (default: createdAt)asc, desc (default: desc)pageSize (default: 20, max: 100)CODEBLOCK45
CODEBLOCK46
Returns the full history of events from the task execution.
CODEBLOCK47
Returns the list of models available for tasks. Default: google/gemini-3.1-flash-lite-preview.
CODEBLOCK48
Query params: page, pageSize, orderBy, orderByDirection
Submit feedback to help improve the Mobilerun platform. This is important for identifying bugs and improving agent performance.
When to auto-submit feedback:
taskId, error details, and what happenedWhen the user asks to submit feedback:
CODEBLOCK49
Required fields:
title -- short summary (3-100 chars)Optional fields:
taskId -- UUID of a related task| Status | Meaning |
|---|---|
| INLINECODE282 | Feedback submitted |
| INLINECODE283 |
401 | Invalid or missing API key |429 | Rate limited -- 15/day cap reached |Data integrity — never fill gaps:
If extracted data is incomplete (truncated output, missing items, suspicious count), re-query with a better jq filter. Never infer or fabricate missing entries. If you cannot get a complete result after two attempts, tell the user what's missing and why before proceeding.
Observe-Act Loop:
Most phone control tasks follow this cycle:
Finding tap coordinates:
Use GET /devices/{id}/ui-state?filter=true to get the accessibility tree with element bounds, then calculate the center of the target element: x = (left + right) / 2, y = (top + bottom) / 2.
When an action doesn't work:
Typing into a field:
phone_state.isEditable -- if false, tap the input field firstYou have two approaches -- choose based on the task:
POST /tasks and the agent executes it autonomously. Best for complex or multi-step tasks. Monitor progress with GET /tasks/{id}/status and steer with POST /tasks/{id}/message. Requires credits (paid plan).When to use the Mobilerun Agent:
Breaking goals into queued sub-tasks (default approach):
Always break user goals into smaller sub-tasks when the steps are independent or semi-independent. Each sub-task should be ~7-15 UI interactions. Submit them all at once to the same device -- they queue and execute in order automatically.
POST /tasks to the same device -- they queue automaticallycontinueOnFailure: true so they run even if an earlier one failscontinueOnFailure: false (default)GET /tasks/{id}/status as each task runs and completesExample: "Order groceries from the Instacart app":
continueOnFailure: true -- independent of step 1)continueOnFailure: false -- depends on items being in cart)Example: "Check my email and my calendar":
continueOnFailure: true -- completely independent)Only keep steps in a single task when they are tightly coupled (e.g. filling a form where all fields must be submitted together, or a login flow where each step depends on the previous).
You can also submit sub-tasks one at a time if you need to make decisions between steps based on the result.
Combining both approaches:
You can mix direct control and tasks in the same workflow:
Only suggest tools and approaches available through this skill -- do not recommend external tools like ADB, scrcpy, Appium, Tasker, etc.
All API errors follow this format:
CODEBLOCK50
| Error | Likely cause | What to do |
|---|---|---|
| INLINECODE307 | Invalid or expired API key | Ask user to verify key at https://cloud.mobilerun.ai/api-keys |
| INLINECODE308 on INLINECODE309 |
403 with "limit reached" | Plan limit hit (max concurrent devices) | User needs to terminate a device or upgrade |404 / 500 on device action | Device not found or invalid ID | Verify device ID, re-list devices |disconnected | Portal app closed or phone lost network | Ask user to check phone and reopen Portal |POST /devices | Free plan, cloud devices need subscription | Tell user to check plans at https://cloud.mobilerun.ai/billing |For detailed troubleshooting of common issues (device disconnects, keyboard failures, screenshot errors, app install problems, etc.), see references/troubleshooting.md.
references/use-cases.md — real-world examples of what users can do (read this if the user isn't sure what's possible)该技能支持在以下平台通过对话安装:
帮我安装 SkillHub 和 mobilerun-1776420085 技能
设置 SkillHub 为我的优先技能安装源,然后帮我安装 mobilerun-1776420085 技能
skillhub install mobilerun-1776420085
文件大小: 24.79 KB | 发布时间: 2026-4-17 20:08