> ## 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 last confirmed phone apps

> Read cached authoritative facts only, no device calls/lease effects. Missing target rows are unknown, not absent. Listings filter to currently visible devices. Last refresh errors never erase last confirmed facts.



## OpenAPI

````yaml /api/openapi.json get /v1/app-library/inventory
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/app-library/inventory:
    get:
      tags:
        - Apps
      summary: Read last confirmed phone apps
      description: >-
        Read cached authoritative facts only, no device calls/lease effects.
        Missing target rows are unknown, not absent. Listings filter to
        currently visible devices. Last refresh errors never erase last
        confirmed facts.
      operationId: listAppInventory
      parameters:
        - name: cursor
          in: query
          schema:
            type: string
          description: >-
            Opaque org/principal/filter/sort/snapshot-bound cursor. Do not
            combine with changed filters. 409 cursor_expired requires restart;
            no hidden mutation.
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          description: Bounded by runtime policy.maxPageSize.
        - name: packageName
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/AppLibraryPackageName'
          description: >-
            Optional exact package. Omission reads the last complete scoped
            phone inventory.
        - name: deviceId
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/AppLibraryId'
          description: Optional single visible device.
        - name: scopeId
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/AppLibraryId'
          description: Optional verified scope; requires deviceId.
      responses:
        '200':
          description: Successful response.
          headers:
            X-Request-Id:
              schema:
                $ref: '#/components/schemas/AppLibraryId'
              description: Sanitized support correlation ID.
            Cache-Control:
              schema:
                type: string
              example: private, no-store
            Idempotency-Replayed:
              schema:
                type: boolean
              description: >-
                Present on mutations; true only when replaying a durable
                original response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppLibraryInventoryPage'
              examples:
                default:
                  value:
                    items:
                      - inventoryId: inventory_01
                        deviceId: device_01
                        scope:
                          scopeId: scope_01
                          label: Managed Android profile
                          kind: android_profile
                          effectScopeVerified: true
                        packageName: com.example.notes
                        presence: absent
                        absenceBasis: explicit_package_absence
                        app: null
                        requestedAt: '2026-09-30T18:00:00Z'
                        receivedAt: '2026-09-30T18:00:00Z'
                        observedAt: null
                        completeness: partial
                        source: device_package_manager
                        freshness:
                          state: fresh
                          basis: adapter_guaranteed_live
                          validUntil: '2026-09-30T18:01:00Z'
                        lastRefresh: null
                    nextCursor: null
                    snapshotId: snapshot_01
                    snapshotExpiresAt: '2026-09-30T18:05:00Z'
        '400':
          description: >-
            Request rejected or outcome requires recovery. See error.retry;
            never infer a job failed from HTTP status. Cross-org/invisible IDs
            use 404.
          headers:
            X-Request-Id:
              schema:
                $ref: '#/components/schemas/AppLibraryId'
              description: Sanitized support correlation ID.
            Retry-After:
              schema:
                type: integer
                minimum: 1
              description: For 429/503, when retry is explicitly allowed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppLibraryErrorEnvelope'
              example:
                error:
                  code: invalid_argument
                  message: >-
                    The request could not be accepted. See the error code for
                    the next step.
                  requestId: req_01
                  field: null
                  retry: none
                  retryAfterSeconds: null
        '401':
          description: >-
            Request rejected or outcome requires recovery. See error.retry;
            never infer a job failed from HTTP status. Cross-org/invisible IDs
            use 404.
          headers:
            X-Request-Id:
              schema:
                $ref: '#/components/schemas/AppLibraryId'
              description: Sanitized support correlation ID.
            Retry-After:
              schema:
                type: integer
                minimum: 1
              description: For 429/503, when retry is explicitly allowed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppLibraryErrorEnvelope'
              example:
                error:
                  code: unauthenticated
                  message: Authenticate before continuing.
                  requestId: req_01
                  field: null
                  retry: none
                  retryAfterSeconds: null
        '403':
          description: >-
            Request rejected or outcome requires recovery. See error.retry;
            never infer a job failed from HTTP status. Cross-org/invisible IDs
            use 404.
          headers:
            X-Request-Id:
              schema:
                $ref: '#/components/schemas/AppLibraryId'
              description: Sanitized support correlation ID.
            Retry-After:
              schema:
                type: integer
                minimum: 1
              description: For 429/503, when retry is explicitly allowed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppLibraryErrorEnvelope'
              example:
                error:
                  code: permission_denied
                  message: You do not have permission for this action.
                  requestId: req_01
                  field: null
                  retry: none
                  retryAfterSeconds: null
        '404':
          description: >-
            Request rejected or outcome requires recovery. See error.retry;
            never infer a job failed from HTTP status. Cross-org/invisible IDs
            use 404.
          headers:
            X-Request-Id:
              schema:
                $ref: '#/components/schemas/AppLibraryId'
              description: Sanitized support correlation ID.
            Retry-After:
              schema:
                type: integer
                minimum: 1
              description: For 429/503, when retry is explicitly allowed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppLibraryErrorEnvelope'
              example:
                error:
                  code: not_found
                  message: Resource not found.
                  requestId: req_01
                  field: null
                  retry: none
                  retryAfterSeconds: null
        '409':
          description: >-
            Request rejected or outcome requires recovery. See error.retry;
            never infer a job failed from HTTP status. Cross-org/invisible IDs
            use 404.
          headers:
            X-Request-Id:
              schema:
                $ref: '#/components/schemas/AppLibraryId'
              description: Sanitized support correlation ID.
            Retry-After:
              schema:
                type: integer
                minimum: 1
              description: For 429/503, when retry is explicitly allowed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppLibraryErrorEnvelope'
              example:
                error:
                  code: idempotency_conflict
                  message: >-
                    The request could not be accepted. See the error code for
                    the next step.
                  requestId: req_01
                  field: null
                  retry: none
                  retryAfterSeconds: null
        '429':
          description: >-
            Request rejected or outcome requires recovery. See error.retry;
            never infer a job failed from HTTP status. Cross-org/invisible IDs
            use 404.
          headers:
            X-Request-Id:
              schema:
                $ref: '#/components/schemas/AppLibraryId'
              description: Sanitized support correlation ID.
            Retry-After:
              schema:
                type: integer
                minimum: 1
              description: For 429/503, when retry is explicitly allowed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppLibraryErrorEnvelope'
              example:
                error:
                  code: rate_limited
                  message: >-
                    The request could not be accepted. See the error code for
                    the next step.
                  requestId: req_01
                  field: null
                  retry: same_key_after_delay
                  retryAfterSeconds: 3
        '503':
          description: >-
            Request rejected or outcome requires recovery. See error.retry;
            never infer a job failed from HTTP status. Cross-org/invisible IDs
            use 404.
          headers:
            X-Request-Id:
              schema:
                $ref: '#/components/schemas/AppLibraryId'
              description: Sanitized support correlation ID.
            Retry-After:
              schema:
                type: integer
                minimum: 1
              description: For 429/503, when retry is explicitly allowed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppLibraryErrorEnvelope'
              example:
                error:
                  code: service_unavailable
                  message: >-
                    The request could not be confirmed. Check the original
                    request before continuing.
                  requestId: req_01
                  field: null
                  retry: same_key_after_delay
                  retryAfterSeconds: 3
      security:
        - bearerAuth: []
components:
  schemas:
    AppLibraryPackageName:
      type: string
      minLength: 1
      maxLength: 255
      pattern: ^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$
      examples:
        - com.example.notes
    AppLibraryId:
      type: string
      pattern: ^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$
      examples:
        - app_01
    AppLibraryInventoryPage:
      type: object
      additionalProperties: false
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/AppLibraryInventoryFact'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
        snapshotId:
          type: string
        snapshotExpiresAt:
          $ref: '#/components/schemas/AppLibraryTimestamp'
      required:
        - items
        - nextCursor
        - snapshotId
        - snapshotExpiresAt
      examples:
        - items:
            - inventoryId: inventory_01
              deviceId: device_01
              scope:
                scopeId: scope_01
                label: Managed Android profile
                kind: android_profile
                effectScopeVerified: true
              packageName: com.example.notes
              presence: absent
              absenceBasis: explicit_package_absence
              app: null
              requestedAt: '2026-09-30T18:00:00Z'
              receivedAt: '2026-09-30T18:00:00Z'
              observedAt: null
              completeness: partial
              source: device_package_manager
              freshness:
                state: fresh
                basis: adapter_guaranteed_live
                validUntil: '2026-09-30T18:01:00Z'
              lastRefresh: null
          nextCursor: null
          snapshotId: snapshot_01
          snapshotExpiresAt: '2026-09-30T18:05:00Z'
    AppLibraryErrorEnvelope:
      type: object
      additionalProperties: false
      properties:
        error:
          $ref: '#/components/schemas/AppLibraryError'
      required:
        - error
      examples:
        - error:
            code: preview_changed
            message: A selected device has changed. Review the devices again.
            requestId: req_01
            field: null
            retry: new_preview
            retryAfterSeconds: null
    AppLibraryInventoryFact:
      type: object
      additionalProperties: false
      properties:
        inventoryId:
          $ref: '#/components/schemas/AppLibraryId'
        deviceId:
          $ref: '#/components/schemas/AppLibraryId'
        scope:
          $ref: '#/components/schemas/AppLibraryScope'
        packageName:
          $ref: '#/components/schemas/AppLibraryPackageName'
        presence:
          type: string
          enum:
            - present
            - absent
            - unknown
        absenceBasis:
          anyOf:
            - type: string
              enum:
                - complete_scope_inventory
                - explicit_package_absence
            - type: 'null'
        app:
          anyOf:
            - $ref: '#/components/schemas/AppLibraryInstalledApp'
            - type: 'null'
        requestedAt:
          $ref: '#/components/schemas/AppLibraryTimestamp'
        receivedAt:
          $ref: '#/components/schemas/AppLibraryTimestamp'
        observedAt:
          anyOf:
            - $ref: '#/components/schemas/AppLibraryTimestamp'
            - type: 'null'
        completeness:
          type: string
          enum:
            - complete
            - partial
            - unknown
        source:
          type: string
          enum:
            - device_package_manager
            - managed_device_service
        freshness:
          $ref: '#/components/schemas/AppLibraryFreshness'
        lastRefresh:
          anyOf:
            - type: object
              additionalProperties: false
              properties:
                refreshId:
                  $ref: '#/components/schemas/AppLibraryId'
                state:
                  type: string
                  enum:
                    - processing
                    - completed
                    - not_completed
                message:
                  anyOf:
                    - type: string
                    - type: 'null'
                finishedAt:
                  anyOf:
                    - $ref: '#/components/schemas/AppLibraryTimestamp'
                    - type: 'null'
              required:
                - refreshId
                - state
                - message
                - finishedAt
            - type: 'null'
      required:
        - inventoryId
        - deviceId
        - scope
        - packageName
        - presence
        - absenceBasis
        - app
        - requestedAt
        - receivedAt
        - observedAt
        - completeness
        - source
        - freshness
        - lastRefresh
      description: >-
        One device/scope/package fact. An absent result requires authoritative
        absenceBasis and applicable complete snapshot or explicit targeted
        absence; omitted rows, failed reads and empty cache never imply absence.
        Failed refresh preserves prior confirmed fact with its original
        timestamps.
      examples:
        - inventoryId: inventory_01
          deviceId: device_01
          scope:
            scopeId: scope_01
            label: Managed Android profile
            kind: android_profile
            effectScopeVerified: true
          packageName: com.example.notes
          presence: absent
          absenceBasis: explicit_package_absence
          app: null
          requestedAt: '2026-09-30T18:00:00Z'
          receivedAt: '2026-09-30T18:00:00Z'
          observedAt: null
          completeness: partial
          source: device_package_manager
          freshness:
            state: fresh
            basis: adapter_guaranteed_live
            validUntil: '2026-09-30T18:01:00Z'
          lastRefresh: null
      allOf:
        - if:
            properties:
              presence:
                const: present
          then:
            properties:
              app:
                $ref: '#/components/schemas/AppLibraryInstalledApp'
              absenceBasis:
                type: 'null'
        - if:
            properties:
              presence:
                const: absent
          then:
            properties:
              app:
                type: 'null'
              absenceBasis:
                type: string
                enum:
                  - complete_scope_inventory
                  - explicit_package_absence
    AppLibraryTimestamp:
      type: string
      format: date-time
      examples:
        - '2026-09-30T18:00:00Z'
    AppLibraryError:
      type: object
      additionalProperties: false
      properties:
        code:
          $ref: '#/components/schemas/AppLibraryErrorCode'
        message:
          type: string
        requestId:
          $ref: '#/components/schemas/AppLibraryId'
        field:
          anyOf:
            - type: string
            - type: 'null'
        retry:
          type: string
          enum:
            - none
            - same_key_after_delay
            - query_original
            - new_preview
        retryAfterSeconds:
          anyOf:
            - type: integer
              minimum: 1
            - type: 'null'
      required:
        - code
        - message
        - requestId
        - field
        - retry
        - retryAfterSeconds
      description: >-
        Stable PhoneBase business/request error. No provider payloads, signed
        URLs, stack traces or internal action keys. HTTP error is distinct from
        a persisted job result.
    AppLibraryScope:
      type: object
      additionalProperties: false
      properties:
        scopeId:
          $ref: '#/components/schemas/AppLibraryId'
        label:
          type: string
        kind:
          type: string
          enum:
            - android_user
            - android_profile
            - provider_instance
        effectScopeVerified:
          type: boolean
      required:
        - scopeId
        - label
        - kind
        - effectScopeVerified
      description: >-
        A stable adapter-verified read/write and actual effect scope. false or
        absent proof never permits writes. No implicit user 0/current/all.
      examples:
        - scopeId: scope_01
          label: Managed Android profile
          kind: android_profile
          effectScopeVerified: true
    AppLibraryInstalledApp:
      type: object
      additionalProperties: false
      properties:
        packageName:
          $ref: '#/components/schemas/AppLibraryPackageName'
        state:
          type: string
          enum:
            - installed
            - installing
            - downloading
            - unknown
        versionCode:
          anyOf:
            - $ref: '#/components/schemas/AppLibraryVersionCode'
            - type: 'null'
        versionName:
          anyOf:
            - type: string
            - type: 'null'
        sha256:
          anyOf:
            - $ref: '#/components/schemas/AppLibrarySha256'
            - type: 'null'
        signingCertificateSha256:
          anyOf:
            - type: array
              items:
                $ref: '#/components/schemas/AppLibrarySha256'
              minItems: 1
            - type: 'null'
        protected:
          anyOf:
            - type: boolean
            - type: 'null'
      required:
        - packageName
        - state
        - versionCode
        - versionName
        - sha256
        - signingCertificateSha256
        - protected
      description: >-
        Sampled installed facts only. null means unconfirmed; never fill from
        library APK or job receipt. Same versionCode without trusted hash is not
        same build.
      examples:
        - packageName: com.example.notes
          state: installed
          versionCode: '41'
          versionName: 2.3.0
          sha256: null
          signingCertificateSha256:
            - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
          protected: false
    AppLibraryFreshness:
      type: object
      additionalProperties: false
      properties:
        state:
          type: string
          enum:
            - fresh
            - stale
            - unknown
        basis:
          type: string
          enum:
            - device_observed_at
            - adapter_guaranteed_live
            - unproven
        validUntil:
          anyOf:
            - $ref: '#/components/schemas/AppLibraryTimestamp'
            - type: 'null'
      required:
        - state
        - basis
        - validUntil
      description: >-
        receivedAt is not observedAt. adapter_guaranteed_live requires
        documented adapter guarantees and bounded acquisition latency; a fast
        HTTP response is insufficient.
      examples:
        - state: fresh
          basis: adapter_guaranteed_live
          validUntil: '2026-09-30T18:01:00Z'
      allOf:
        - if:
            properties:
              state:
                const: fresh
          then:
            properties:
              basis:
                type: string
                enum:
                  - device_observed_at
                  - adapter_guaranteed_live
              validUntil:
                $ref: '#/components/schemas/AppLibraryTimestamp'
    AppLibraryErrorCode:
      type: string
      enum:
        - invalid_argument
        - unauthenticated
        - permission_denied
        - not_found
        - idempotency_conflict
        - request_in_progress
        - idempotency_window_expired
        - cursor_expired
        - cursor_mismatch
        - quota_exceeded
        - version_unavailable
        - version_referenced
        - upload_not_ready
        - upload_expired
        - upload_rejected
        - duplicate_archived
        - deletion_pending
        - checksum_mismatch
        - unsupported_format
        - metadata_unavailable
        - preview_expired
        - preview_changed
        - target_ineligible
        - retry_not_safe
        - rate_limited
        - feature_unavailable
        - service_unavailable
        - precondition_failed
        - file_too_large
    AppLibraryVersionCode:
      type: string
      pattern: ^(0|[1-9][0-9]*)$
      maxLength: 19
      description: >-
        Canonical nonnegative decimal integer string. Current proposed parser
        range is 0..9223372036854775807 inclusive; server MUST check this
        numeric upper bound, never use floating point or versionName to compare.
        Out-of-range metadata is rejected, never coerced to zero.
      examples:
        - '9007199254740993'
    AppLibrarySha256:
      type: string
      pattern: ^[a-f0-9]{64}$
      examples:
        - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  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.