> ## 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.

# Preview removing a Lusor Phone

> Quotes removing this Lusor Phone from the Phone plan: the plan
quantity falls by one and the unused rest of the current period is
credited to the account balance (applied to the next invoice).
Nothing changes yet. The quote expires after 10 minutes; confirm it
with `POST /devices/{deviceId}/remove`.

Refused with 409 `device_busy` while a session is running on the
phone, and 409 `last_phone` for the plan's last phone (cancel the
plan in billing management instead).




## OpenAPI

````yaml /api-reference/openapi.yaml post /v1/workspaces/{workspaceId}/devices/{deviceId}/remove/preview
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/{deviceId}/remove/preview:
    post:
      tags:
        - Devices
      summary: Preview removing a Lusor Phone
      description: |
        Quotes removing this Lusor Phone from the Phone plan: the plan
        quantity falls by one and the unused rest of the current period is
        credited to the account balance (applied to the next invoice).
        Nothing changes yet. The quote expires after 10 minutes; confirm it
        with `POST /devices/{deviceId}/remove`.

        Refused with 409 `device_busy` while a session is running on the
        phone, and 409 `last_phone` for the plan's last phone (cancel the
        plan in billing management instead).
      operationId: previewDeviceRemove
      parameters:
        - $ref: '#/components/parameters/WorkspaceIdPath'
        - $ref: '#/components/parameters/DeviceIdPath'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
      responses:
        '200':
          description: The quote
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/DeviceChangePreview'
                required:
                  - data
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/DeviceNotFound'
        '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.
    DeviceIdPath:
      name: deviceId
      in: path
      required: true
      schema:
        type: string
      description: Device id (paired into the workspace).
  schemas:
    DeviceChangePreview:
      type: object
      required:
        - previewId
        - quantity
        - currency
        - dueToday
        - creditToday
        - totalAtRenewal
        - expiresAt
        - internetExitCountry
      properties:
        previewId:
          type: string
          format: uuid
        quantity:
          type: integer
          description: Lusor Phones on the plan after the change.
        currency:
          type: string
          enum:
            - usd
        dueToday:
          type: integer
          description: US cents charged when the change is confirmed (0 for a removal).
        creditToday:
          type: integer
          description: >-
            US cents credited to the account balance when the change is
            confirmed (0 for a deploy).
        totalAtRenewal:
          type: integer
          description: US cents of the next full invoice after the change.
        expiresAt:
          type: integer
          description: Unix seconds after which the quote cannot be confirmed.
        internetExitCountry:
          type:
            - string
            - 'null'
          description: The new phone's Internet exit country; null for a removal.
        inboxQuantityChange:
          type: integer
          description: >-
            Paid email addresses ($2/mo each) the same change adds or removes.
            Each Lusor Phone includes one address, so a removal that leaves more
            addresses than phones is +1 and added phones that cover paid
            addresses are negative; 0 when no address changes billing. Absent on
            older previews.
        subtotal:
          type: integer
          description: >-
            US cents, the invoice subtotal before invoice-level discounts and
            tax, with item-level discounts already applied (negative for a
            removal's credit).
        tax:
          type: integer
          description: >-
            US cents of tax added on top of that subtotal (exclusive tax;
            inclusive tax is already inside the subtotal), so that subtotal -
            discountToday + tax is the invoice total.
        discountToday:
          type: integer
          description: >-
            US cents of discounts itemized on the invoice billed now. Discounts
            already netted into prorated amounts are not counted. Absent on
            older previews.
        discountAtRenewal:
          type: integer
          description: >-
            US cents of discounts itemized on the next full invoice. Absent on
            older previews.
        subtotalAtRenewal:
          type: integer
          description: >-
            US cents, the next full invoice's subtotal before invoice-level
            discounts and added tax, so that subtotalAtRenewal -
            discountAtRenewal + taxAtRenewal = totalAtRenewal. Absent on older
            previews.
        taxAtRenewal:
          type: integer
          description: >-
            US cents of tax added on top of that subtotal (exclusive tax;
            inclusive tax is already inside the subtotal). Absent on older
            previews.
    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'
    Forbidden:
      description: The caller may not perform this action.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    DeviceNotFound:
      description: |
        `device_not_found`: no Lusor Phone of this workspace's plan has this
        id (absent, in another workspace, or not a Lusor Phone).
      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.