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

# Deploy the quoted Lusor Phones

> Confirms a deploy quote: the Phone plan quantity rises by the quoted
number of phones and the quoted amount is charged to the saved card
now. If the payment does not go through, the quantity stays
unchanged, `paymentPending` is true and no phone is deployed;
complete the payment in billing management.

Each new phone appears in `GET /devices` once the payment is
confirmed (seconds), and is ready to use once it has deployed
(minutes).




## OpenAPI

````yaml /api-reference/openapi.yaml post /v1/workspaces/{workspaceId}/devices/deploy
openapi: 3.1.0
info:
  title: Lusor API
  version: 0.1.0
  description: |
    Run AI agents on real phones, or drive a phone directly with
    deterministic actions.

    ## Authentication

    Every `/v1/*` endpoint except `GET /v1/health` requires an API key.
    Create one in the Lusor dashboard under **Settings → API Keys**. A key
    is shown once and is bound to one workspace. Revoking it there stops it
    working immediately.

    Send it in either header:

    - `X-Api-Key: <your API key>`
    - `Authorization: Bearer <your API key>`

    Because a key is bound to its workspace, `workspaceId` query and body
    parameters are optional for API-key callers. A request that names a
    different workspace returns an opaque `404`.

    ## Runs

    Use `POST /v1/agents/{id}/turns` to start or continue an agent run.
    Read the run under `/v1/sessions/{id}`; poll
    `GET /v1/sessions/{id}/status` or `GET /v1/sessions/{id}/events` for
    progress.

    ## Errors

    Errors are JSON: `{ "error": "<code>", "message": "<text>" }`. A
    request that fails validation returns `400` in one of two shapes,
    depending on the endpoint: `{ "error": "validation_error", "details":
    { ... } }` (direct phone control actions, for example), or
    `{ "error": "bad_request", "message": "<text>" }` (agent endpoints, for
    example). Branch on `error`.
  contact:
    name: Lusor
    url: https://lusor.ai
  license:
    name: Proprietary
    url: https://lusor.ai/terms
servers:
  - url: https://api.lusor.ai
    description: Production
security:
  - ApiKey: []
  - BearerAuth: []
tags:
  - name: Agents
    description: Persistent agents, the phone each one uses, and their turns (runs).
  - name: Secrets
    description: |
      Secrets saved in a workspace (passwords, sign-in details). A run uses
      them by name through `secrets`; values never travel in a request.
  - name: Sessions
    description: |
      Run records: list, inspect, stop, and read events and artifacts. Start
      a run with `POST /v1/agents/{id}/turns`, or without an agent through
      `POST /v1/workspaces/{workspaceId}/devices/{deviceId}/sessions`.
  - name: LiveView
    description: |
      View-only WebRTC live view of a session's phone: signaling, ICE
      servers, and stream control. API keys get live view only on the
      customer's own phone, not on a Lusor Phone.
  - name: Devices
    description: |
      The phones in your workspace, and reads of their current screen and
      UI tree.
  - name: DeviceVerbs
    description: |
      Direct phone control: deterministic actions (tap, swipe, type, key,
      clear, back, home, recent, screenshot, apps/start, apps/stop) and
      chains of them. On a Lusor Phone, API-key calls meter phone minutes.
  - name: Shell
    description: |
      Restricted shell escape hatch. Commands are checked against a
      denylist and audit-logged. No command output is returned.
  - name: Datasets
    description: Lists a run produced, saved as reusable Datasets.
  - name: Skills
    description: Reusable automation skills (Markdown documents).
  - name: PhoneNumbers
    description: |
      Workspace phone numbers and their stored SMS messages. Read-only for
      API keys.
  - name: Emails
    description: |
      Workspace Agent Mail addresses and stored inbound mail.
      Read-only: there is no compose, reply, or send endpoint, and message
      bodies are returned as raw plain text without HTML execution, remote
      asset fetches, or code extraction.
  - name: Workspaces
    description: The workspace an API key is bound to.
  - name: Catalog
    description: The catalog of apps that can be installed on a Lusor Phone.
  - name: Health
    description: Service health check.
paths:
  /v1/workspaces/{workspaceId}/devices/deploy:
    post:
      tags:
        - Devices
      summary: Deploy the quoted Lusor Phones
      description: |
        Confirms a deploy quote: the Phone plan quantity rises by the quoted
        number of phones and the quoted amount is charged to the saved card
        now. If the payment does not go through, the quantity stays
        unchanged, `paymentPending` is true and no phone is deployed;
        complete the payment in billing management.

        Each new phone appears in `GET /devices` once the payment is
        confirmed (seconds), and is ready to use once it has deployed
        (minutes).
      operationId: deployDevice
      parameters:
        - $ref: '#/components/parameters/WorkspaceIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeviceChangeConfirmInput'
      responses:
        '200':
          description: The plan after the change
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/DeviceChangeResult'
                required:
                  - data
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PhoneLimit'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/DeviceChangeConflict'
        '503':
          $ref: '#/components/responses/BillingUnavailable'
components:
  parameters:
    WorkspaceIdPath:
      name: workspaceId
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Workspace UUID. Caller must have access.
  schemas:
    DeviceChangeConfirmInput:
      type: object
      additionalProperties: false
      required:
        - previewId
      properties:
        previewId:
          type: string
          format: uuid
          description: The `previewId` of an unexpired quote.
    DeviceChangeResult:
      type: object
      required:
        - status
        - quantity
        - paymentPending
        - workspaceId
      properties:
        status:
          type: string
          description: Billing status after the change (`active` when it applied).
        quantity:
          type: integer
          description: Lusor Phones on the plan, as paid. Unchanged while `paymentPending`.
        paymentPending:
          type: boolean
          description: >-
            The charge did not go through yet; nothing changed. Complete it in
            billing management.
        workspaceId:
          type: string
    ValidationErrorResponse:
      type: object
      required:
        - error
        - details
      properties:
        error:
          type: string
          example: validation_error
        details:
          type: object
          additionalProperties: true
    ErrorResponse:
      type: object
      required:
        - error
        - message
      properties:
        error:
          type: string
        message:
          type: string
  responses:
    ValidationError:
      description: |
        Request validation failed. Either `validation_error` with per-field
        `details`, or `bad_request` with a `message`, depending on the
        endpoint.
      content:
        application/json:
          schema:
            anyOf:
              - $ref: '#/components/schemas/ValidationErrorResponse'
              - $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Authentication required or invalid credentials.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    PhoneLimit:
      description: |
        `tier_phone_limit`: the plan's phone limit would be exceeded; the
        body names the limit and the tier to upgrade to.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: The caller may not perform this action.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Resource not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    DeviceChangeConflict:
      description: >
        The change was refused; nothing was charged or removed by this call.
        `error` is one of:

        `default_workspace_only` (deploy outside the Default workspace),

        `no_subscription` (no active Phone plan),

        `checkout_open` (a checkout is still open),

        `payment_pending` (a payment on the plan is unpaid),

        `preview_expired` (the quote expired, is unknown, or the plan changed
        since: preview again),

        `device_busy` (a session is running on the phone),

        `device_releasing` (the phone is already being released),

        `last_phone` (the plan's last phone: cancel the plan in billing
        management instead),

        or `billing_conflict` (the plan needs support reconciliation).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    BillingUnavailable:
      description: |
        `billing_unavailable`: billing is temporarily unavailable. On a
        preview nothing changed. On a confirmation the outcome is unknown:
        retry the confirmation with the SAME `previewId`, which is never
        applied twice, and do not request a new quote (it could add a second
        phone).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: X-Api-Key
      description: Workspace API key, created in the dashboard under Settings → API Keys.
    BearerAuth:
      type: http
      scheme: bearer
      description: >-
        The same workspace API key, sent as `Authorization: Bearer <your API
        key>`.

````

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