Device
Read the phone's device info, read and set the clipboard, location, locale and timezone, and reboot or reset a phone.
These requests read or change the phone itself. Each needs a ready phone. Reads need the phones:read scope, except the clipboard's, which needs phones:control like every change; a reset needs phones:delete. Device settings shows them in context.
Some phones don't offer every feature on this page. A request for one the phone doesn't offer fails with 422 feature_unavailable, and details.feature names it, such as device_info, location, timezone, locale or reset. Nothing was sent to the phone.
Each one that writes is sent to the phone once and never retried. When the phone doesn't confirm it, the request fails with 502 action_outcome_unknown: read the setting back, or observe, before you send it again. The readiness and phone-service errors in Common errors apply to every request on this page.
Get device info
GET /v1/phones/{id}/device needs the phones:read scope.
The phone's brand, model, Android version, screen size in device pixels and mobile carrier. Any of them is null when the phone doesn't report it. Nothing else about the phone's hardware or identity is returned.
curl https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/device \
-H "Authorization: Bearer $PHONEBOX_API_KEY"{
"brand": "google",
"model": "Pixel 9",
"android_version": "15",
"screen": { "width": 1080, "height": 2424, "density": 420 },
"carrier": "T-Mobile"
}Read the clipboard
GET /v1/phones/{id}/clipboard needs the phones:control scope.
curl https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/clipboard \
-H "Authorization: Bearer $PHONEBOX_API_KEY"{
"text": "Order 58213 confirmed"
}| Error | When |
|---|---|
409 clipboard_unavailable | The phone's default text input was switched off, so its clipboard can't be read. Switch it back in the phone's Settings, or read the text on screen with observe. Setting the clipboard still works. |
Set the clipboard
PUT /v1/phones/{id}/clipboard needs the phones:control scope.
| Field | Type | Default | Rules |
|---|---|---|---|
text | string | None | 1 to 10,000 characters. Whitespace is preserved. |
Clearing the clipboard with empty text is not supported. Sending {"text":""} returns
400 validation_failed before any phone action runs. Do not retry an empty write or restart the phone to clear it.
{
"text": "https://example.com/reset?code=7F3K9Q"
}curl -X PUT https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/clipboard \
-H "Authorization: Bearer $PHONEBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "https://example.com/reset?code=7F3K9Q"}'The answer repeats the text. Activity records it only as [redacted].
{
"text": "https://example.com/reset?code=7F3K9Q"
}| Error | When |
|---|---|
400 validation_failed | text is missing, empty or longer than 10,000 characters. No phone action ran. |
Read the location
GET /v1/phones/{id}/location needs the phones:read scope.
The location the phone reports to apps. Until you set one, it is the phone's default, New York.
curl https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/location \
-H "Authorization: Bearer $PHONEBOX_API_KEY"{
"lat": 40.7128,
"lng": -74.006
}Set the location
PUT /v1/phones/{id}/location needs the phones:control scope.
Sets the location that the phone reports to apps and, by default, the phone's timezone to the one at that location, so its clock matches the place. The timezone comes from the nearest large place, or, far out at sea, from the longitude; near a border it can be the neighbour's, so check timezone in the answer and set the timezone yourself if it is wrong.
| Field | Type | Default | Rules |
|---|---|---|---|
lat | number | None | Latitude in decimal degrees, from -90 to 90. |
lng | number | None | Longitude in decimal degrees, from -180 to 180. |
timezone_from_location | boolean | true | Also set the timezone found at the location. Send false to change only the location. |
{
"lat": 40.748817,
"lng": -73.985428
}curl -X PUT https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/location \
-H "Authorization: Bearer $PHONEBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"lat": 40.748817, "lng": -73.985428}'The answer names the timezone it set, or null when timezone_from_location was false, or the phone can't change its timezone or refused the one found.
{
"lat": 40.748817,
"lng": -73.985428,
"timezone": "America/New_York"
}The location and the timezone are two changes, each sent once. When the timezone fails after the location was set, the error has details.location_set: true and details.timezone, and next is the PUT /v1/phones/{id}/timezone request that sets it.
| Error | When |
|---|---|
400 validation_failed | lat or lng is missing or out of range. |
422 feature_unavailable | The phone can't change its location. |
Reset the location
DELETE /v1/phones/{id}/location needs the phones:control scope.
Goes back to the phone's default location, New York, and, when the phone can change its timezone, to the timezone found there, so its clock matches the place as after Set the location. The two changes are each sent once. When the timezone fails after the location was reset, the error has details.location_reset: true and details.timezone, and next is the PUT /v1/phones/{id}/timezone request that sets it.
curl -X DELETE https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/location \
-H "Authorization: Bearer $PHONEBOX_API_KEY"{
"reset": true
}Read the locale
GET /v1/phones/{id}/locale needs the phones:read scope.
curl https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/locale \
-H "Authorization: Bearer $PHONEBOX_API_KEY"{
"locale": "en-US"
}Set the locale
PUT /v1/phones/{id}/locale needs the phones:control scope.
Changes the phone's language and region. The phone's interface restarts, so observe again before you act, and expect its labels in the new language.
| Field | Type | Default | Rules |
|---|---|---|---|
locale | string | None | A language code of 2 or 3 lowercase letters, then an optional script such as Hant, then an optional region: two capital letters or a 3-digit area code. Examples are en-US, de-DE, zh-Hant-TW and es-419. |
{
"locale": "fr-FR"
}curl -X PUT https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/locale \
-H "Authorization: Bearer $PHONEBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"locale": "fr-FR"}'{
"locale": "fr-FR"
}| Error | When |
|---|---|
400 validation_failed | locale is missing or doesn't have that shape. |
Read the timezone
GET /v1/phones/{id}/timezone needs the phones:read scope.
The phone's IANA timezone, or null while it uses its default.
curl https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/timezone \
-H "Authorization: Bearer $PHONEBOX_API_KEY"{
"timezone": "Europe/Paris"
}Set the timezone
PUT /v1/phones/{id}/timezone needs the phones:control scope.
| Field | Type | Default | Rules |
|---|---|---|---|
timezone | string | None | An IANA time zone name, spelled exactly, such as Europe/Paris or America/New_York. Offsets such as +05:30 aren't accepted. |
{
"timezone": "Europe/Paris"
}curl -X PUT https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/timezone \
-H "Authorization: Bearer $PHONEBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"timezone": "Europe/Paris"}'{
"timezone": "Europe/Paris"
}| Error | When |
|---|---|
400 validation_failed | timezone is missing or isn't a time zone name. |
Reboot
POST /v1/phones/{id}/reboot needs the phones:control scope.
Restarts the phone, keeping everything on it. It takes no body and answers 202 Accepted with the phone, now starting, which has to prove it is ready again, as after a start. Wait with GET /v1/phones/{id}?wait=ready before you use it. The session stays open, so the reboot's time is billed like any other.
curl -X POST https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/reboot \
-H "Authorization: Bearer $PHONEBOX_API_KEY"{
"id": "ph_7kx2m6q4v3ta",
"object": "phone",
"name": "checkout-test",
"status": "starting",
"country": null,
"metadata": { "customer": "acme" },
"created_at": "2026-09-29T10:00:00.412Z",
"last_active_at": "2026-09-29T14:26:13.402Z",
"session": {
"id": "ses_x7c3v5b2n6m4",
"started_at": "2026-09-29T14:20:02.118Z",
"ready_at": "2026-09-29T14:20:41.309Z",
"idle_timeout": 900,
"max_duration": 7200,
"parks_at": "2026-09-29T14:41:13.402Z",
"park_reason": "idle",
"reserved_usd": "7.200000"
},
"failure": null
}A phone can reboot once every 10 minutes. A second reboot within that time fails with 429 rate_limited, and both details.retry_after_seconds and the Retry-After header give the seconds left:
{
"error": {
"type": "rate_limit_error",
"code": "rate_limited",
"message": "Too many requests. Wait a moment and try again.",
"retryable": true,
"next": "A phone can reboot once every 10 minutes.",
"request_id": "req_n6p2r4s7t3v5",
"details": { "retry_after_seconds": 347 }
}
}When the phone service refuses a reboot outright, the phone didn't restart, and the 10-minute allowance isn't used up.
| Error | When |
|---|---|
429 rate_limited | The phone rebooted less than 10 minutes ago. |
502 action_outcome_unknown | The phone service didn't confirm the reboot, so the phone may be restarting. next is GET /v1/phones/{id}?wait=ready: wait for ready, and never send a second reboot to find out. |
Reset
POST /v1/phones/{id}/reset needs the phones:delete scope, which only Admin keys have.
Wipes the phone back to a clean state: its installed apps and their data, its files, its signed-in accounts and its clipboard are deleted, and can't be brought back. The phone keeps its ID and its session. It takes no body and answers 202 Accepted with the phone, now starting, and wiped, which lists what was deleted. The phone comes back in about a minute: wait with GET /v1/phones/{id}?wait=ready before you use it. The session stays open, so the reset's time is billed like any other.
The reset is sent once and never retried. A second reset while the phone is still starting fails with 409 phone_starting, and one within a minute of the last fails with 429 rate_limited.
curl -X POST https://phonebox.dev/v1/phones/ph_7kx2m6q4v3ta/reset \
-H "Authorization: Bearer $PHONEBOX_API_KEY"{
"phone": {
"id": "ph_7kx2m6q4v3ta",
"object": "phone",
"name": "checkout-test",
"status": "starting",
"country": null,
"metadata": { "customer": "acme" },
"created_at": "2026-09-29T10:00:00.412Z",
"last_active_at": "2026-09-29T14:26:13.402Z",
"session": {
"id": "ses_x7c3v5b2n6m4",
"started_at": "2026-09-29T14:20:02.118Z",
"ready_at": "2026-09-29T14:20:41.309Z",
"idle_timeout": 900,
"max_duration": 7200,
"parks_at": "2026-09-29T14:41:13.402Z",
"park_reason": "idle",
"reserved_usd": "7.200000"
},
"failure": null
},
"wiped": ["apps", "files", "accounts", "clipboard"]
}| Error | When |
|---|---|
403 insufficient_scope | The key doesn't have phones:delete. Use an Admin key. |
422 feature_unavailable | The phone can't be reset. Delete it and create a new one instead. |
502 action_outcome_unknown | The phone service didn't confirm the reset, so the phone may be resetting. next is GET /v1/phones/{id}?wait=ready: wait for ready, and never send a second reset to find out. |