Phonebox / Docs
API reference

Device

Read the phone's device info, read and set the clipboard, location, locale and timezone, and reboot or reset a phone.

View as Markdown

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"
Response: device
{
  "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"
Response: clipboard
{
  "text": "Order 58213 confirmed"
}
ErrorWhen
409 clipboard_unavailableThe 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.

FieldTypeDefaultRules
textstringNone1 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.

PUT /v1/phones/{id}/clipboard
{
  "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].

Response: clipboard
{
  "text": "https://example.com/reset?code=7F3K9Q"
}
ErrorWhen
400 validation_failedtext 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"
Response: current location
{
  "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.

FieldTypeDefaultRules
latnumberNoneLatitude in decimal degrees, from -90 to 90.
lngnumberNoneLongitude in decimal degrees, from -180 to 180.
timezone_from_locationbooleantrueAlso set the timezone found at the location. Send false to change only the location.
PUT /v1/phones/{id}/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.

Response: location
{
  "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.

ErrorWhen
400 validation_failedlat or lng is missing or out of range.
422 feature_unavailableThe 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"
Response: reset location
{
  "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"
Response: current locale
{
  "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.

FieldTypeDefaultRules
localestringNoneA 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.
PUT /v1/phones/{id}/locale
{
  "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"}'
Response: locale
{
  "locale": "fr-FR"
}
ErrorWhen
400 validation_failedlocale 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"
Response: current timezone
{
  "timezone": "Europe/Paris"
}

Set the timezone

PUT /v1/phones/{id}/timezone needs the phones:control scope.

FieldTypeDefaultRules
timezonestringNoneAn IANA time zone name, spelled exactly, such as Europe/Paris or America/New_York. Offsets such as +05:30 aren't accepted.
PUT /v1/phones/{id}/timezone
{
  "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"}'
Response: timezone
{
  "timezone": "Europe/Paris"
}
ErrorWhen
400 validation_failedtimezone 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"
Response: 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
}

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:

Response: error
{
  "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.

ErrorWhen
429 rate_limitedThe phone rebooted less than 10 minutes ago.
502 action_outcome_unknownThe 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"
Response: reset
{
  "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"]
}
ErrorWhen
403 insufficient_scopeThe key doesn't have phones:delete. Use an Admin key.
422 feature_unavailableThe phone can't be reset. Delete it and create a new one instead.
502 action_outcome_unknownThe 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.

On this page