Apps
Search the app library, list a phone's installed apps, install apps from the library or your own APK in the background, follow the installs, and uninstall apps.
Every app request on a phone needs a ready phone. Apps and their data stay on the phone while it is parked. Apps shows how to open, close and link into them with actions.
List apps
GET /v1/phones/{id}/apps needs the phones:read scope.
Lists every installed app, the system apps that came with the phone included.
curl https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/apps \
-H "Authorization: Bearer $PHONEBOX_API_KEY"{
"data": [
{ "package": "com.android.chrome", "label": "Chrome", "version_name": "129.0.6668.100", "version_code": 666810033, "system": true },
{ "package": "com.android.settings", "label": "Settings", "version_name": "14", "version_code": 34, "system": true },
{ "package": "org.wikipedia", "label": "Wikipedia", "version_name": "2.7.50502", "version_code": 50502, "system": false }
]
}| Field | Type | Meaning |
|---|---|---|
package | string | The Android package name. |
label | string | The name the launcher shows. |
version_name | string | The version as the app displays it. |
version_code | integer | The version as a number. |
system | boolean | true for an app that came with the phone. |
The readiness and phone-service errors in Common errors apply.
Search the app library
GET /v1/apps/library needs the phones:read scope.
Searches Phonebox's app library: the apps a phone installs by package with Install an app. It holds store and system apps, the same for every project. It names no phone, and it isn't Google Play: an app that isn't here installs from the Play Store app on the phone.
| Parameter | Type | Default | Rules |
|---|---|---|---|
query | string | None | Part of a package or app name, up to 100 characters, such as chrome. Without it, the whole library is listed. |
cursor | string | None | The next_cursor of the previous page, sent back unchanged. |
curl "https://phonebox.dev/v1/apps/library?query=chrome" \
-H "Authorization: Bearer $PHONEBOX_API_KEY"Each page holds up to 50 apps; next_cursor is null on the last one. Phonebox refreshes the library every few hours. While it loads for the first time, a search answers 429 rate_limited: search again after Retry-After.
{
"data": [
{ "package": "com.android.chrome", "label": "Chrome", "version_code": 666810033, "version_name": "134.0.6998.135" }
],
"next_cursor": null
}Install an app
POST /v1/phones/{id}/apps needs the phones:control scope.
Starts installing an app and answers at once, while the install continues in the background. It installs one of two things:
- An app from Phonebox's app library, by its package name: the library's own version of it. It doesn't reach Google Play, or any uploaded APK: a Play Store app installs through the Play Store app on the phone, as Apps describes, and your own APK by
upload. - Your own APK, by an upload whose status is
ready. It installs the package the APK declares at the APK'sversionCode, and its entry in List app installs succeeds only once the phone lists that package at that version.
| Field | Type | Default | Rules |
|---|---|---|---|
package | string | None | An Android package name: at least two parts separated by dots, starting with a letter, up to 255 characters. Give this or upload. |
upload | string | None | An upload's ID, upl_…, from this project. Give this or package. |
replace | boolean | false | With upload: uninstall the app first when it is on the phone, which deletes its data and sign-ins. Use it for a build signed with another key, or an older versionCode. |
{
"package": "com.example.app"
}curl https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/apps \
-H "Authorization: Bearer $PHONEBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"package": "com.example.app"}'It answers 202 Accepted:
{
"package": "com.example.app",
"status": "running"
}An install by upload answers with its upload too:
{
"package": "com.example.app",
"upload": "upl_q3m7x2k6v4ta",
"install": "ins_7m2k6q3x4v5a",
"status": "running"
}curl https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/apps \
-H "Authorization: Bearer $PHONEBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"upload": "upl_q3m7x2k6v4ta"}'Follow the install with List app installs. Until it has finished, open_app fails with app_not_found.
| Error | When |
|---|---|
400 validation_failed | The body names neither or both of package and upload, package isn't a valid package name, or replace comes without upload. |
404 upload_not_found | No upload with this ID exists in the project. |
409 upload_not_ready | The upload isn't ready: details.status says what it is, and next what to do. With retryable: true, the upload is being prepared for this phone: send the install again in a minute. |
409 version_downgrade | The phone has a newer versionCode of the upload's app: details.installed_version_code and details.version_code. Nothing ran. Build with a higher versionCode, or send replace: true. |
413 payload_too_large | The body is larger than 100 KB. |
409 install_in_progress | Another install is still running on the phone, which runs at most two installs at once, and one per app. This error is retryable. Installs take a few seconds, so send it again shortly, a few times at most. List app installs shows when the running install has finished, or failed. details.package names the app. |
422 app_not_available | The app isn't in Phonebox's app library. details.package names it, and next says to use the Play Store app on the phone or upload the APK. Your own uploads install only by upload. |
502 action_outcome_unknown | The phone service didn't confirm the install, so it may have started. List the installs before you try again. For an install by upload, details.install names its row there, which Phonebox follows to its end: don't send the install again until that row has ended. |
With replace, Phonebox first checks that the phone can start another install, so an install_in_progress refusal then comes before anything is uninstalled. An error that comes after the uninstall has details.uninstalled: true: the app and its data are already gone, and next says when sending the same request again is safe.
The readiness and phone-service errors in Common errors apply too.
List app installs
GET /v1/phones/{id}/apps/installs needs the phones:read scope.
Lists the phone's recent install attempts.
curl https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/apps/installs \
-H "Authorization: Bearer $PHONEBOX_API_KEY"{
"data": [
{ "package": "org.wikipedia", "upload": null, "install": null, "status": "succeeded", "error": null, "next": null, "started_at": "2026-09-29T10:02:10.518Z", "updated_at": "2026-09-29T10:02:51.064Z" },
{ "package": "com.example.app", "upload": "upl_q3m7x2k6v4ta", "install": "ins_7m2k6q3x4v5a", "status": "succeeded", "error": null, "next": null, "started_at": "2026-09-29T10:03:02.000Z", "updated_at": "2026-09-29T10:03:05.000Z" }
]
}| Field | Type | Meaning |
|---|---|---|
package | string | The package being installed. |
upload | string or null | The upload an install by upload put on the phone, or null for an app from the library. |
install | string or null | An install by upload's own ID, ins_…, which its start answered with. Find your install's row by it. null for an app from the library. |
status | string | running, succeeded or failed. An install by upload succeeds only once the phone lists its package at the upload's version_code. |
error | string or null | For a failed install, its reason as a machine code, such as install_failed. Otherwise null. |
next | string or null | What to do about a failed install by upload. When the phone already had the app, it says a build signed with another key may be installed, and to uninstall it or install again with replace: true. |
started_at, updated_at | timestamp or null | When the install started and last changed, or null when that isn't known. |
The readiness and phone-service errors in Common errors apply.
Uninstall an app
DELETE /v1/phones/{id}/apps/{package} needs the phones:control scope.
Removes an app with all of its data and sign-ins. {package} is the app's package name.
curl -X DELETE https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/apps/org.wikipedia \
-H "Authorization: Bearer $PHONEBOX_API_KEY"{
"package": "org.wikipedia",
"uninstalled": true
}| Error | When |
|---|---|
400 validation_failed | {package} isn't a valid package name. |
404 app_not_found | The app isn't installed. details.package names it. |
502 action_outcome_unknown | The phone service didn't confirm the uninstall. List the apps to see whether it happened. |
The readiness and phone-service errors in Common errors apply too.