> ## Documentation Index
> Fetch the complete documentation index at: https://docs.phoneuse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Read one device

> One device's current status and capabilities without enumerating the fleet. Every status is a valid answer here, including `offline`: this route reports state, it does not refuse devices that are not usable right now.

A device your org does not own answers 404 exactly as an id that never existed does.

Hosted deployments only. A local checkout does not mount this route, so calling it there is a 404.



## OpenAPI

````yaml /api/openapi.json get /v1/devices/{deviceId}
openapi: 3.1.0
info:
  title: Phonebase HTTP API
  version: 0.1.0
  summary: >-
    Every HTTP operation the control plane mounts, generated from the app's own
    route table.
  description: >-
    The control plane exposes two HTTP surfaces, and reading the boundary
    between them matters more than reading any single endpoint.


    The CONTROL surface is what your backend and your operators call. It takes a
    bearer token in the `Authorization` header, and two credential channels
    resolve to the same authorization context: an OAuth 2.1 access token for
    interactive clients, or an org scoped API key for headless ones.


    The DEVICE surface, under `/vrx/{runToken}/v1`, is what a worker calls while
    executing one run. Its credential is the run token in the url path, and it
    reads no `Authorization` header at all.


    Device control itself is not REST. An agent taps, swipes and reads the
    screen over the Model Context Protocol at `POST /mcp`, one tool call at a
    time. There is no `POST /v1/devices/{id}/tap`, and if you are looking for
    one, start with the tool reference instead.


    Operations marked `"x-phonebase-modes": ["hosted"]` exist only on a hosted
    deployment; a local checkout does not mount them.
servers:
  - url: '{origin}'
    description: Your control plane origin.
    variables:
      origin:
        default: https://mcp.phonebase.co
        enum:
          - https://mcp.phonebase.co
          - http://127.0.0.1:8788
        description: >-
          The hosted deployment serves the public origin its metadata document
          advertises, and that is the default here. Select the loopback entry
          instead when you are running a local checkout, which serves the port
          `PORT` names.
security: []
tags:
  - name: Apps
    description: Save immutable application builds and manage explicit device operations.
  - name: Managed Proxy
    description: >-
      Organization-owned fixed egress resources, independent one-time orders and
      verified network relationships.
  - name: Health
    description: Liveness, unauthenticated.
  - name: Gateway enrollment
    description: >-
      How a new gateway computer trades a one-time enrollment code for its
      tunnel credential. Unauthenticated: the code is the credential.
  - name: Gateway releases
    description: >-
      What a gateway computer installs: the current edge release of a channel.
      Called with the gateway's own credential.
  - name: MCP
    description: The JSON-RPC endpoint an agent drives devices through.
  - name: Copilot
    description: >-
      Drive a device by chatting. A signed in person's own conversations,
      streamed. Hosted deployments only.
  - name: Discovery
    description: Where to authenticate. Served on hosted deployments only.
  - name: Runs
    description: Start, watch and steer a run.
  - name: API keys
    description: Mint and revoke headless credentials. Hosted deployments only.
  - name: Device surface
    description: What a worker calls with its run token to observe and act.
  - name: Usage
    description: Read your org's own receipts and consumption. Hosted deployments only.
  - name: Devices
    description: Read the fleet your credential can see. Hosted deployments only.
  - name: Notifications
    description: >-
      What a person needs to know when they are not watching, and which emails
      they receive. Hosted deployments only.
  - name: Webhooks
    description: >-
      Subscribe your own system to what happens here, and read what was
      delivered. Hosted deployments only.
  - name: Provisioning
    description: >-
      Whether an organisation is provisioned, and how it becomes so. Hosted
      deployments only, and both operations answer a declared 503 on a
      deployment that cannot provision yet.
paths:
  /v1/devices/{deviceId}:
    get:
      tags:
        - Devices
      summary: Read one device
      description: >-
        One device's current status and capabilities without enumerating the
        fleet. Every status is a valid answer here, including `offline`: this
        route reports state, it does not refuse devices that are not usable
        right now.


        A device your org does not own answers 404 exactly as an id that never
        existed does.


        Hosted deployments only. A local checkout does not mount this route, so
        calling it there is a 404.
      operationId: getV1DevicesByDeviceId
      parameters:
        - name: deviceId
          in: path
          required: true
          description: The `deviceId` from a listing.
          schema:
            type: string
      responses:
        '200':
          description: The device.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceDetail'
        '401':
          description: No credential, or one that was refused.
          headers:
            WWW-Authenticate:
              description: >-
                A bearer challenge. On a hosted deployment it names the metadata
                document to discover the authorization server from, and it adds
                `error="invalid_token"` when a credential was presented and
                rejected.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedError'
        '403':
          description: Reading devices needs `devices:read`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No such device that this credential can see.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: >-
            Over the request budget for this caller. Hosted deployments only.
            Which budget applied depends on whether this request carried an
            `Authorization` header, not on which endpoint it was aimed at. Wait
            `Retry-After` seconds and retry the same request: the refusal
            happens before any work, so nothing was partially applied. See
            /api/rate-limits.
          headers:
            Retry-After:
              description: >-
                Whole seconds until one request is available again. Computed
                from the caller's own remaining allowance, not a fixed constant.
              schema:
                type: string
            RateLimit-Limit:
              description: >-
                The budget in effect for this caller class, in requests per
                minute.
              schema:
                type: string
            RateLimit-Remaining:
              description: >-
                Whole requests that were still available when this request was
                admitted. Zero on a 429.
              schema:
                type: string
            RateLimit-Reset:
              description: >-
                Seconds until the allowance is back to full. Longer than
                `Retry-After`, which is the wait for a single request.
              schema:
                type: string
            RateLimit-Policy:
              description: The budget and its window, as `<limit>;w=60`.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateLimitedError'
        '503':
          description: >-
            The authentication backend could not be reached. The service fails
            closed: an infrastructure fault is refused, never waved through.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - bearerAuth: []
components:
  schemas:
    DeviceDetail:
      type: object
      properties:
        deviceId:
          type: string
          description: The id every other endpoint addresses this device by.
        backendKind:
          type: string
          enum:
            - adb_cloud
            - adb_real
            - emulator
            - robot_hand
            - esp32_hid
            - byod_portal
          description: How this device is reached.
        kind:
          type: string
          enum:
            - emulator
            - cloud_phone
            - physical
            - robot
          description: >-
            What this device IS, in the words a person uses: an `emulator`, a
            `cloud_phone` in somebody's rack, a `physical` handset, or one
            driven by a `robot` arm. `backendKind` beside it names the TRANSPORT
            instead, which is the right key for picking an adapter and the wrong
            word for a label: `adb_real` and `adb_cloud` are one sentence to an
            operator and two different objects to a customer. Derive nothing
            from `backendKind` that this field already answers.


            THESE ARE NOT THE PURCHASABLE TIERS. What an organisation can buy is
            a separate, shorter list; the words overlap and the sets do not.
        status:
          type: string
          enum:
            - online
            - offline
            - unauthorized
            - unknown
          description: >-
            Whether the control plane can currently REACH the device. `online`
            does not mean you can drive it: an online device may be held right
            now by an unexpired lease, or by our staff (then it carries
            `staffHold`), and a device your org owns that discovery cannot see
            is `offline` rather than missing.
        capabilities:
          description: >-
            What THIS device can do, which is always a subset of what its
            backend kind can do. Consult it before acting: backends differ, and
            a robot hand has camera frames but no UI tree.
          oneOf:
            - type: object
              properties:
                fidelityTier:
                  type: string
                  const: software
                  description: >-
                    How input reaches the device. Branch on this before reading
                    `physical`.
                actions:
                  type: object
                  properties:
                    tap:
                      type: boolean
                    swipe:
                      type: boolean
                    typeText:
                      description: >-
                        How text reaches the device. `native` is OS level
                        injection, `per_key_visual` is an actuator pressing one
                        on screen key at a time, `false` is unsupported.
                      oneOf:
                        - type: string
                          enum:
                            - native
                            - per_key_visual
                        - type: boolean
                          const: false
                    pressKey:
                      description: >-
                        The exact keys this device accepts, or `false` when it
                        presses none. An empty list is never emitted.
                      oneOf:
                        - type: array
                          minItems: 1
                          items:
                            type: string
                            enum:
                              - home
                              - back
                              - enter
                              - delete
                              - app_switch
                              - power
                              - volume_up
                              - volume_down
                            description: A pressable key.
                        - type: boolean
                          const: false
                    openApp:
                      description: >-
                        `intent` launches by app id through the OS, `visual`
                        finds the icon and taps it, `false` is unsupported.
                      oneOf:
                        - type: string
                          enum:
                            - intent
                            - visual
                        - type: boolean
                          const: false
                    installApp:
                      description: >-
                        `apk` means this device can be handed an Android package
                        to install. `false` means it cannot, and on a robot
                        driven phone that is permanent rather than pending: an
                        actuator touches the screen and has no channel into the
                        operating system, so the only route for an app is a
                        person using the store on screen.
                      oneOf:
                        - type: string
                          enum:
                            - apk
                        - type: boolean
                          const: false
                    longPress:
                      description: >-
                        Whether this device can HOLD a point down rather than
                        tapping it. A hold is sent as a zero-distance swipe, so
                        `swipe: true` does not answer it: an adb-backed phone
                        keeps the finger down, while a robot arm reads the
                        duration as travel time and performs a brief press, so
                        it declares `false` and refuses a long press instead of
                        landing something else under that name.
                      type: boolean
                    wake:
                      description: >-
                        Whether this device's screen can be turned on from here
                        (`POST /v1/devices/{deviceId}/wake`). Its own field
                        rather than a `pressKey` entry, because `power` is a
                        toggle and nothing here can read whether the screen is
                        already on. `false` means the wake route refuses this
                        device. True for a cloud phone reached over the
                        provider's API, an emulator, and an Android handset on
                        USB; false for a robot-driven phone and, for now, for a
                        phone behind the gateway tunnel.
                      type: boolean
                    stop:
                      description: >-
                        Whether `POST /v1/devices/{deviceId}/stop` can halt this
                        device: empty its queued motions, cancel the one in
                        progress and lift the pen. Only a robot-driven phone has
                        motion to stop, so this is true only there, and only
                        when the arm's controller supports it; everywhere else
                        the stop route refuses.
                      type: boolean
                  required:
                    - tap
                    - swipe
                    - typeText
                    - pressKey
                    - openApp
                    - installApp
                    - longPress
                    - wake
                    - stop
                  additionalProperties: false
                observations:
                  type: object
                  properties:
                    screenshot:
                      description: >-
                        `framebuffer` is a pixel exact OS capture, `camera` is a
                        photograph of the physical screen, `false` is no visual
                        observation.
                      oneOf:
                        - type: string
                          enum:
                            - framebuffer
                            - camera
                        - type: boolean
                          const: false
                    uiTree:
                      description: >-
                        Source of the element tree, or `false` for a device with
                        no structural view.
                      oneOf:
                        - type: string
                          enum:
                            - uiautomator
                            - a11y
                        - type: boolean
                          const: false
                    framesAreReferenceable:
                      type: boolean
                      description: >-
                        Whether screenshots carry a frame token an action can
                        bind to with `observedFrame`.
                    freshnessMs:
                      type:
                        - integer
                        - 'null'
                      description: >-
                        Maximum age IN MILLISECONDS a bound frame may have
                        before an action is refused as stale, or null for no age
                        bound at all. The control plane puts no clock on a
                        frame: a frame is refused when the screen has moved on
                        (a newer screenshot supersedes it, or another hand
                        acted) rather than when time has passed. An integer here
                        is the bound of a resource that expires frames itself,
                        as a robot-driven phone's rig may declare, and such a
                        refusal answers `expired`. null on a robot-driven phone
                        means no age bound, or a rig too old to say. Meaningful
                        only when `framesAreReferenceable` is true; read no
                        meaning into it otherwise, and in particular do not
                        expect 0.
                    stream:
                      description: >-
                        `webrtc` means `POST /v1/devices/{deviceId}/stream` can
                        hand back a live video session for this device (the
                        provider's channel for a cloud phone, the arm's
                        published camera track for a robot-driven phone);
                        `false` means still images only. It describes the
                        device: a deployment with no media server still answers
                        the stream route with the screenshot fallback, so read
                        that route's `transport` for what a session actually is.
                      oneOf:
                        - type: string
                          enum:
                            - webrtc
                        - type: boolean
                          const: false
                  required:
                    - screenshot
                    - uiTree
                    - framesAreReferenceable
                    - freshnessMs
                    - stream
                  additionalProperties: false
                os:
                  type: string
                  enum:
                    - android
                    - ios
                coordSpace:
                  type: object
                  properties:
                    width:
                      type: integer
                      description: Grid width in COORDINATE UNITS, not device pixels.
                    height:
                      type: integer
                      description: Grid height in COORDINATE UNITS, not device pixels.
                  required:
                    - width
                    - height
                  additionalProperties: false
                  description: >-
                    The grid every coordinate in the API speaks. Callers never
                    see device pixels; the backend converts.
              required:
                - fidelityTier
                - actions
                - observations
                - os
                - coordSpace
              additionalProperties: false
            - type: object
              properties:
                fidelityTier:
                  type: string
                  const: hid
                  description: >-
                    How input reaches the device. Branch on this before reading
                    `physical`.
                actions:
                  type: object
                  properties:
                    tap:
                      type: boolean
                    swipe:
                      type: boolean
                    typeText:
                      description: >-
                        How text reaches the device. `native` is OS level
                        injection, `per_key_visual` is an actuator pressing one
                        on screen key at a time, `false` is unsupported.
                      oneOf:
                        - type: string
                          enum:
                            - native
                            - per_key_visual
                        - type: boolean
                          const: false
                    pressKey:
                      description: >-
                        The exact keys this device accepts, or `false` when it
                        presses none. An empty list is never emitted.
                      oneOf:
                        - type: array
                          minItems: 1
                          items:
                            type: string
                            enum:
                              - home
                              - back
                              - enter
                              - delete
                              - app_switch
                              - power
                              - volume_up
                              - volume_down
                            description: A pressable key.
                        - type: boolean
                          const: false
                    openApp:
                      description: >-
                        `intent` launches by app id through the OS, `visual`
                        finds the icon and taps it, `false` is unsupported.
                      oneOf:
                        - type: string
                          enum:
                            - intent
                            - visual
                        - type: boolean
                          const: false
                    installApp:
                      description: >-
                        `apk` means this device can be handed an Android package
                        to install. `false` means it cannot, and on a robot
                        driven phone that is permanent rather than pending: an
                        actuator touches the screen and has no channel into the
                        operating system, so the only route for an app is a
                        person using the store on screen.
                      oneOf:
                        - type: string
                          enum:
                            - apk
                        - type: boolean
                          const: false
                    longPress:
                      description: >-
                        Whether this device can HOLD a point down rather than
                        tapping it. A hold is sent as a zero-distance swipe, so
                        `swipe: true` does not answer it: an adb-backed phone
                        keeps the finger down, while a robot arm reads the
                        duration as travel time and performs a brief press, so
                        it declares `false` and refuses a long press instead of
                        landing something else under that name.
                      type: boolean
                    wake:
                      description: >-
                        Whether this device's screen can be turned on from here
                        (`POST /v1/devices/{deviceId}/wake`). Its own field
                        rather than a `pressKey` entry, because `power` is a
                        toggle and nothing here can read whether the screen is
                        already on. `false` means the wake route refuses this
                        device. True for a cloud phone reached over the
                        provider's API, an emulator, and an Android handset on
                        USB; false for a robot-driven phone and, for now, for a
                        phone behind the gateway tunnel.
                      type: boolean
                    stop:
                      description: >-
                        Whether `POST /v1/devices/{deviceId}/stop` can halt this
                        device: empty its queued motions, cancel the one in
                        progress and lift the pen. Only a robot-driven phone has
                        motion to stop, so this is true only there, and only
                        when the arm's controller supports it; everywhere else
                        the stop route refuses.
                      type: boolean
                  required:
                    - tap
                    - swipe
                    - typeText
                    - pressKey
                    - openApp
                    - installApp
                    - longPress
                    - wake
                    - stop
                  additionalProperties: false
                observations:
                  type: object
                  properties:
                    screenshot:
                      description: >-
                        `framebuffer` is a pixel exact OS capture, `camera` is a
                        photograph of the physical screen, `false` is no visual
                        observation.
                      oneOf:
                        - type: string
                          enum:
                            - framebuffer
                            - camera
                        - type: boolean
                          const: false
                    uiTree:
                      description: >-
                        Source of the element tree, or `false` for a device with
                        no structural view.
                      oneOf:
                        - type: string
                          enum:
                            - uiautomator
                            - a11y
                        - type: boolean
                          const: false
                    framesAreReferenceable:
                      type: boolean
                      description: >-
                        Whether screenshots carry a frame token an action can
                        bind to with `observedFrame`.
                    freshnessMs:
                      type:
                        - integer
                        - 'null'
                      description: >-
                        Maximum age IN MILLISECONDS a bound frame may have
                        before an action is refused as stale, or null for no age
                        bound at all. The control plane puts no clock on a
                        frame: a frame is refused when the screen has moved on
                        (a newer screenshot supersedes it, or another hand
                        acted) rather than when time has passed. An integer here
                        is the bound of a resource that expires frames itself,
                        as a robot-driven phone's rig may declare, and such a
                        refusal answers `expired`. null on a robot-driven phone
                        means no age bound, or a rig too old to say. Meaningful
                        only when `framesAreReferenceable` is true; read no
                        meaning into it otherwise, and in particular do not
                        expect 0.
                    stream:
                      description: >-
                        `webrtc` means `POST /v1/devices/{deviceId}/stream` can
                        hand back a live video session for this device (the
                        provider's channel for a cloud phone, the arm's
                        published camera track for a robot-driven phone);
                        `false` means still images only. It describes the
                        device: a deployment with no media server still answers
                        the stream route with the screenshot fallback, so read
                        that route's `transport` for what a session actually is.
                      oneOf:
                        - type: string
                          enum:
                            - webrtc
                        - type: boolean
                          const: false
                  required:
                    - screenshot
                    - uiTree
                    - framesAreReferenceable
                    - freshnessMs
                    - stream
                  additionalProperties: false
                os:
                  type: string
                  enum:
                    - android
                    - ios
                coordSpace:
                  type: object
                  properties:
                    width:
                      type: integer
                      description: Grid width in COORDINATE UNITS, not device pixels.
                    height:
                      type: integer
                      description: Grid height in COORDINATE UNITS, not device pixels.
                  required:
                    - width
                    - height
                  additionalProperties: false
                  description: >-
                    The grid every coordinate in the API speaks. Callers never
                    see device pixels; the backend converts.
              required:
                - fidelityTier
                - actions
                - observations
                - os
                - coordSpace
              additionalProperties: false
            - type: object
              properties:
                fidelityTier:
                  type: string
                  const: physical
                  description: >-
                    How input reaches the device. Branch on this before reading
                    `physical`.
                actions:
                  type: object
                  properties:
                    tap:
                      type: boolean
                    swipe:
                      type: boolean
                    typeText:
                      description: >-
                        How text reaches the device. `native` is OS level
                        injection, `per_key_visual` is an actuator pressing one
                        on screen key at a time, `false` is unsupported.
                      oneOf:
                        - type: string
                          enum:
                            - native
                            - per_key_visual
                        - type: boolean
                          const: false
                    pressKey:
                      description: >-
                        The exact keys this device accepts, or `false` when it
                        presses none. An empty list is never emitted.
                      oneOf:
                        - type: array
                          minItems: 1
                          items:
                            type: string
                            enum:
                              - home
                              - back
                              - enter
                              - delete
                              - app_switch
                              - power
                              - volume_up
                              - volume_down
                            description: A pressable key.
                        - type: boolean
                          const: false
                    openApp:
                      description: >-
                        `intent` launches by app id through the OS, `visual`
                        finds the icon and taps it, `false` is unsupported.
                      oneOf:
                        - type: string
                          enum:
                            - intent
                            - visual
                        - type: boolean
                          const: false
                    installApp:
                      description: >-
                        `apk` means this device can be handed an Android package
                        to install. `false` means it cannot, and on a robot
                        driven phone that is permanent rather than pending: an
                        actuator touches the screen and has no channel into the
                        operating system, so the only route for an app is a
                        person using the store on screen.
                      oneOf:
                        - type: string
                          enum:
                            - apk
                        - type: boolean
                          const: false
                    longPress:
                      description: >-
                        Whether this device can HOLD a point down rather than
                        tapping it. A hold is sent as a zero-distance swipe, so
                        `swipe: true` does not answer it: an adb-backed phone
                        keeps the finger down, while a robot arm reads the
                        duration as travel time and performs a brief press, so
                        it declares `false` and refuses a long press instead of
                        landing something else under that name.
                      type: boolean
                    wake:
                      description: >-
                        Whether this device's screen can be turned on from here
                        (`POST /v1/devices/{deviceId}/wake`). Its own field
                        rather than a `pressKey` entry, because `power` is a
                        toggle and nothing here can read whether the screen is
                        already on. `false` means the wake route refuses this
                        device. True for a cloud phone reached over the
                        provider's API, an emulator, and an Android handset on
                        USB; false for a robot-driven phone and, for now, for a
                        phone behind the gateway tunnel.
                      type: boolean
                    stop:
                      description: >-
                        Whether `POST /v1/devices/{deviceId}/stop` can halt this
                        device: empty its queued motions, cancel the one in
                        progress and lift the pen. Only a robot-driven phone has
                        motion to stop, so this is true only there, and only
                        when the arm's controller supports it; everywhere else
                        the stop route refuses.
                      type: boolean
                  required:
                    - tap
                    - swipe
                    - typeText
                    - pressKey
                    - openApp
                    - installApp
                    - longPress
                    - wake
                    - stop
                  additionalProperties: false
                observations:
                  type: object
                  properties:
                    screenshot:
                      description: >-
                        `framebuffer` is a pixel exact OS capture, `camera` is a
                        photograph of the physical screen, `false` is no visual
                        observation.
                      oneOf:
                        - type: string
                          enum:
                            - framebuffer
                            - camera
                        - type: boolean
                          const: false
                    uiTree:
                      description: >-
                        Source of the element tree, or `false` for a device with
                        no structural view.
                      oneOf:
                        - type: string
                          enum:
                            - uiautomator
                            - a11y
                        - type: boolean
                          const: false
                    framesAreReferenceable:
                      type: boolean
                      description: >-
                        Whether screenshots carry a frame token an action can
                        bind to with `observedFrame`.
                    freshnessMs:
                      type:
                        - integer
                        - 'null'
                      description: >-
                        Maximum age IN MILLISECONDS a bound frame may have
                        before an action is refused as stale, or null for no age
                        bound at all. The control plane puts no clock on a
                        frame: a frame is refused when the screen has moved on
                        (a newer screenshot supersedes it, or another hand
                        acted) rather than when time has passed. An integer here
                        is the bound of a resource that expires frames itself,
                        as a robot-driven phone's rig may declare, and such a
                        refusal answers `expired`. null on a robot-driven phone
                        means no age bound, or a rig too old to say. Meaningful
                        only when `framesAreReferenceable` is true; read no
                        meaning into it otherwise, and in particular do not
                        expect 0.
                    stream:
                      description: >-
                        `webrtc` means `POST /v1/devices/{deviceId}/stream` can
                        hand back a live video session for this device (the
                        provider's channel for a cloud phone, the arm's
                        published camera track for a robot-driven phone);
                        `false` means still images only. It describes the
                        device: a deployment with no media server still answers
                        the stream route with the screenshot fallback, so read
                        that route's `transport` for what a session actually is.
                      oneOf:
                        - type: string
                          enum:
                            - webrtc
                        - type: boolean
                          const: false
                  required:
                    - screenshot
                    - uiTree
                    - framesAreReferenceable
                    - freshnessMs
                    - stream
                  additionalProperties: false
                os:
                  type: string
                  enum:
                    - android
                    - ios
                coordSpace:
                  type: object
                  properties:
                    width:
                      type: integer
                      description: Grid width in COORDINATE UNITS, not device pixels.
                    height:
                      type: integer
                      description: Grid height in COORDINATE UNITS, not device pixels.
                  required:
                    - width
                    - height
                  additionalProperties: false
                  description: >-
                    The grid every coordinate in the API speaks. Callers never
                    see device pixels; the backend converts.
                physical:
                  type: object
                  properties:
                    hover:
                      type: boolean
                      description: >-
                        Hold the actuator over a point without touching the
                        screen.
                    pressDepth:
                      type: boolean
                      description: Control press force beyond the calibrated default.
                    queueControl:
                      type: boolean
                      description: Inspect or reorder queued motions before they execute.
                  required:
                    - hover
                    - pressDepth
                    - queueControl
                  additionalProperties: false
              required:
                - fidelityTier
                - actions
                - observations
                - os
                - coordSpace
                - physical
              additionalProperties: false
        metadata:
          type: object
          additionalProperties: true
          description: >-
            Backend defined detail, echoed as the backend reported it. Its keys
            are not part of this contract.
        displayName:
          type: string
          description: >-
            What you have chosen to call this device, set through `PATCH
            /v1/devices/{deviceId}`. ABSENT when nobody has named it, which is
            not the same as an empty name: show `deviceId` in its place. Nothing
            in the control plane reads it, and it never addresses the device.
        info:
          $ref: '#/components/schemas/DeviceInfo'
        staffHold:
          $ref: '#/components/schemas/DeviceStaffHold'
        holdsUnknown:
          type: boolean
          const: true
          description: >-
            Present, and `true`, only on a robot arm whose staff hold we could
            not check just now. The device reads as normal, but no `staffHold`
            then means not known rather than not held. Absent on every other
            device, since nothing else can be held. A lease request still
            refuses a held device, so the cost of acting on this reading is a
            refusal, never a lease or a charge.
      required:
        - deviceId
        - backendKind
        - kind
        - status
        - capabilities
        - metadata
      additionalProperties: false
      description: >-
        One device, as `DeviceSummary`, plus `holdsUnknown` when its staff hold
        could not be read. The same shape the `get_device` tool returns.
    UnauthorizedError:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              const: unauthorized
              description: Always `unauthorized` on this response.
            reason:
              type: string
              enum:
                - missing_credentials
                - malformed_credentials
                - invalid_token
                - token_expired
                - wrong_audience
                - missing_org
                - missing_org_role
                - unknown_key
                - revoked_key
                - expired_key
              description: The closed set of ways authentication can fail.
            hint:
              type: string
              description: >-
                A next step in prose, on the reasons where there is one to give:
                `revoked_key`, `expired_key`, `missing_org_role`, and
                `invalid_token` when the account is not a member of the named
                organization. Absent otherwise, so branch on `reason` and show
                this if it is there.
          required:
            - code
            - reason
          additionalProperties: false
      required:
        - error
      additionalProperties: false
      description: >-
        A refused credential. `unknown_key` covers both a keyId nobody has and a
        secret that does not match, because telling those apart would enumerate
        keys for you. Revocation and expiry are distinguishable, since you owned
        that key.
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - backend_resolution_failed
                - backend_not_implemented
                - device_unavailable
                - device_disconnected
                - lease_mismatch
                - lease_required
                - capability_unsupported
                - invalid_argument
                - backend_command_failed
                - idempotency_conflict
                - action_outcome_unknown
                - stale_frame
                - internal_error
                - unauthorized
                - permission_denied
                - run_not_found
                - frame_not_found
                - device_busy
                - admission_unavailable
                - checkout_unavailable
                - checkout_customer_balance
                - checkout_in_progress
                - quote_changed
                - payment_in_progress
                - out_of_stock
                - wrong_state
                - instance_not_served
                - reverification_required
                - not_found
                - receipt_not_found
                - rate_limited
                - payload_too_large
                - origin_not_allowed
                - api_key_from_browser_origin
                - key_predates_instance_attribution
                - org_not_served_by_instance
                - tenant_binding_unavailable
                - tenant_binding_unsettled
                - fleet_at_capacity
                - forbidden
                - service_unavailable
                - invalid_signature
                - invalid_event
                - not_settled
              description: >-
                The closed set of codes this service answers with on purpose, on
                MCP tools and REST alike (D152). What each one means and when
                you get it is on the errors page.
            message:
              type: string
              description: A human readable summary. Do not branch on it, branch on `code`.
            retryable:
              type: boolean
              description: >-
                Whether re-issuing the same call unchanged is meaningful. It is
                a property of the occurrence, not of the code, so read the field
                rather than inferring it.
          required:
            - code
            - message
          additionalProperties: true
          description: >-
            Specific errors add their own fields, such as `deviceId`, `reason`
            or `capability`. Read the ones you branch on and ignore the rest.
      required:
        - error
      additionalProperties: false
      description: >-
        The control surface error envelope. Every non 2xx JSON body on this
        surface has this shape.
    RateLimitedError:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              const: rate_limited
              description: Always `rate_limited` on this response.
            message:
              type: string
              description: >-
                A human readable summary. The numbers are in the headers, not in
                here.
          required:
            - code
            - message
          additionalProperties: false
      required:
        - error
      additionalProperties: false
      description: >-
        Over the request budget. Raised by the rate limiter before any handler
        runs, which is why its code is not one of the action codes: nothing was
        attempted, so no action failed.
    DeviceInfo:
      type: object
      properties:
        model:
          type: string
          description: Marketing name as the device reports it, for example `Pixel 4 XL`.
        androidVersion:
          type: string
          description: >-
            Android RELEASE, for example `13`, the number Settings → About
            shows. Never the SDK integer (33).
        osVersion:
          type: string
          description: >-
            The operating system's release on any platform, for example `17.5`
            on an iPhone. On Android it is the same value as `androidVersion`.
            Today only a robot-driven phone reports it; a cloud phone's reading
            carries `androidVersion` alone.
        resolution:
          type: object
          properties:
            width:
              type: integer
            height:
              type: integer
          required:
            - width
            - height
          additionalProperties: false
          description: >-
            The screen's real size in device pixels.


            NOT the grid you address actions in. Taps and swipes are sent in
            `capabilities.coordSpace`, a normalised 1000x1000 space, and
            converting a tap through THIS rectangle instead will land it in the
            wrong place. It is also not a screenshot's `pixelSize`, which is one
            observation's dimensions in whatever orientation it was taken in.
            Use this to say what the phone IS, not to aim at it.
        carrier:
          type:
            - string
            - 'null'
          description: >-
            Mobile operator. `null` means the device answered and has none,
            which a phone with no SIM legitimately is, and is distinct from the
            field being absent, which means it did not answer at all.
      required: []
      additionalProperties: false
      description: >-
        WHAT THE PHONE SAYS IT IS, rather than what the control plane knows
        about it (D126): the facts a person reads off Settings → About.


        READ ONCE AND CACHED. It is filled THE FIRST TIME THE DEVICE IS
        ACQUIRED, and retried on each later `acquire` while it is still missing;
        nothing probes the device to answer a listing, because a per-device
        round trip on `GET /v1/devices` would be paid by every caller for a
        field most do not read. A reading can therefore appear on a device that
        had none a moment ago, and the acquire response that triggered it may
        still not carry it.


        THE ABSENCES ARE THREE DIFFERENT ANSWERS and collapsing them will report
        a phone wrong. `info` itself absent: nobody has ever asked this device.
        Render `Not reported yet`, never a blank model. A FIELD absent: it was
        asked and did not answer for that one. `carrier: null`: asked, answered,
        and the answer is that it has no operator.


        Which fields arrive depends on the transport, not on the phone. A cloud
        phone reached over the provider's HTTPS API has no route that returns a
        shell command's output, so `resolution`, which is a `wm size` call and
        not a system property, is absent there today even though the screen
        plainly has one. A robot-driven phone is read by the rig next to it:
        `model`, `osVersion` and, on Android, `androidVersion` and `resolution`.
        An iPhone on a rig reports no `resolution`, and no robot-driven phone
        reports `carrier`.
    DeviceStaffHold:
      type: object
      properties:
        reason:
          type: string
          const: leased
          description: >-
            The `reason` a lease request on this device is refused with while
            the hold lasts.
        retryable:
          type: boolean
          description: >-
            Whether trying again later can succeed. `false` only for a device
            that has not been set up for use, which no wait fixes.
        hint:
          type: string
          description: >-
            What is going on, in words to show a person. The same `hint` the
            refusal carries.
      required:
        - reason
        - retryable
        - hint
      additionalProperties: false
      description: >-
        OUR STAFF HOLD THIS DEVICE right now: a calibration, maintenance, an on
        site check. Present only while the hold lasts, and only on a robot arm.
        Also absent when `holdsUnknown` is `true`: then we could not check, and
        absent means not known.


        It is the refusal you would get, shown before you ask: a lease request
        (`POST /v1/devices/{deviceId}/lease`, `acquire_device`) or a renewal is
        refused `409 device_unavailable` with exactly this `reason`, `retryable`
        and `hint`. Pick another device, or wait and list again when `retryable`
        is `true`.


        It is NOT availability. A device another customer is using shows no
        hold, and `status` stays reachability.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        The control surface credential. Send `Authorization: Bearer <token>`.


        Two kinds of token are accepted and they are told apart by shape, not by
        a separate header. A token beginning `pbk_` is an org scoped API key,
        whose public half and secret half are generated together and of which
        only a hash of the secret is ever stored; anything else is treated as an
        OAuth 2.1 access token and verified against the authorization server's
        keys.


        Both resolve to the same context: an org, a principal and a set of
        scopes. Nothing downstream branches on which channel you used, with one
        deliberate exception, key management, which requires a signed-in person
        so that a key can never mint another key.


        Scopes are enforced when MCP tools are REGISTERED rather than when they
        are called, so a tool your credential cannot use is absent from
        `tools/list` rather than refused mid gesture.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.