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

# Add and remove Lusor Phones

> Add Lusor Phones to your Phone plan, or remove one, with a price preview first.

You can add and remove Lusor Phones with the API. Each change is two calls: a **preview** that quotes the price and changes nothing, then a **confirm** that applies that exact quote.

An API key can do this on its own workspace. In the app, only organization admins can.

> [!NOTE]
>
> The API changes the number of phones on an existing Phone plan. Your workspace needs an active Phone plan first. Start one from the **Phones** page in the [Lusor app](https://app.lusor.ai) with **Add phone**. Without a plan, these calls return `409 no_subscription`.

## Add phones

**1. Preview**

`POST /v1/workspaces/{workspaceId}/devices/deploy/preview`

| Field | Meaning |
| - | - |
| `quantity` | How many Lusor Phones to add, 1 to 100. Default 1. |
| `internetExitCountry` | Where the new phones' Internet traffic exits, as a lowercase two-letter country code. Default `us`. Must be one of the `rotating` countries from [`GET …/internet/locations`](https://docs.lusor.ai/phones/internet-and-static-ip). |
| `internetConnection` | `persistent` (default) gives each phone a static IP where one is sold in that country. `rotating` adds the phones without one. |

Nothing is charged. The quote is valid for 10 minutes.

**2. Confirm**

`POST /v1/workspaces/{workspaceId}/devices/deploy` with `{ "previewId": "…" }` from the quote.

The plan's phone count rises by the quoted number and the quoted amount is charged to your saved card at once.

**3. Wait for the phones**

Each new phone appears in `GET /v1/devices` once the payment is confirmed, usually within seconds. It is ready to use once it has been set up, which takes minutes.

```bash cURL theme={null}
curl https://api.lusor.ai/v1/workspaces/$WS/devices/deploy/preview \
  -H "X-Api-Key: $LUSOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "quantity": 1, "internetExitCountry": "us" }'

# Check the quote, then:
curl https://api.lusor.ai/v1/workspaces/$WS/devices/deploy \
  -H "X-Api-Key: $LUSOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "previewId": "<data.previewId from the preview>" }'
```

```python Python theme={null}
import os, requests

API = f"https://api.lusor.ai/v1/workspaces/{os.environ['WS']}/devices"
H = {"X-Api-Key": os.environ["LUSOR_API_KEY"]}

quote = requests.post(f"{API}/deploy/preview", headers=H,
                      json={"quantity": 1, "internetExitCountry": "us"}).json()["data"]
print("due today (cents):", quote["dueToday"], "next invoice:", quote["totalAtRenewal"])

result = requests.post(f"{API}/deploy", headers=H,
                       json={"previewId": quote["previewId"]}).json()["data"]
if result["paymentPending"]:
    print("Payment did not go through. Complete it in billing management.")
```

```javascript Node.js theme={null}
const API = `https://api.lusor.ai/v1/workspaces/${process.env.WS}/devices`;
const H = { "X-Api-Key": process.env.LUSOR_API_KEY, "Content-Type": "application/json" };

const quote = (await (await fetch(`${API}/deploy/preview`, {
  method: "POST", headers: H,
  body: JSON.stringify({ quantity: 1, internetExitCountry: "us" }),
})).json()).data;
console.log("due today (cents):", quote.dueToday, "next invoice:", quote.totalAtRenewal);

const result = (await (await fetch(`${API}/deploy`, {
  method: "POST", headers: H,
  body: JSON.stringify({ previewId: quote.previewId }),
})).json()).data;
if (result.paymentPending) console.log("Payment did not go through. Complete it in billing management.");
```

Phones are always added to your organization's default workspace. A preview for any other workspace returns `409 default_workspace_only`.

## Remove a phone

**1. Preview**

`POST /v1/workspaces/{workspaceId}/devices/{deviceId}/remove/preview` with an empty body.

The quote shows the plan's phone count after the removal and the credit for the unused rest of the current period. The credit goes to your account balance and is applied to your next invoice. Nothing changes yet. The quote is valid for 10 minutes.

**2. Confirm**

`POST /v1/workspaces/{workspaceId}/devices/{deviceId}/remove` with `{ "previewId": "…" }`.

That phone, and no other, stops accepting runs at once. The plan's phone count falls by one, and the phone is then wiped and released.

> [!WARNING]
>
> Removing a phone erases it. Its apps and data cannot be recovered.

A removal is refused with `409 device_busy` while a session is running on the phone, including a session that started after the preview. The plan's last phone cannot be removed this way (`409 last_phone`). Cancel the plan in billing management instead.

## The quote

Amounts are in US cents.

| Field | Meaning |
| - | - |
| `previewId` | Send this to confirm. |
| `quantity` | Lusor Phones on the plan after the change. |
| `dueToday` | Charged when you confirm. Prorated for the rest of the current period. `0` for a removal. |
| `creditToday` | Credited to your account balance when you confirm. `0` for an addition. |
| `totalAtRenewal` | The next full invoice after the change. |
| `subtotal`, `discountToday`, `tax` | How today's invoice adds up: `subtotal - discountToday + tax`. |
| `subtotalAtRenewal`, `discountAtRenewal`, `taxAtRenewal` | The same for the next full invoice. |
| `inboxQuantityChange` | How many paid Agent Mail addresses the same change adds (positive) or removes (negative). Each Lusor Phone includes one address. |
| `internetExitCountry` | The new phones' exit country. `null` for a removal. |
| `expiresAt` | Unix seconds after which the quote cannot be confirmed. |

## Payment

* If the charge does not go through, the confirm returns `paymentPending: true`. Nothing changed: the phone count stays the same and no phone is added. Complete the payment in billing management.
* `503 billing_unavailable` on a preview means nothing changed. Try again.
* `503 billing_unavailable` on a **confirm** means the outcome is unknown. Retry the confirm with the **same** `previewId`, which is never applied twice. Do not request a new quote, because confirming it could add a second phone.

## Errors

| Status | `error` | Meaning |
| - | - | - |
| `402` | `tier_phone_limit` | Your plan's phone limit would be exceeded. The message names the limit and the plan to upgrade to. |
| `409` | `default_workspace_only` | Phones can only be added to the Default workspace. |
| `409` | `no_subscription` | No active Phone plan. Start one in the app. |
| `409` | `checkout_open` | A checkout is still open. |
| `409` | `payment_pending` | A payment on the plan is unpaid. |
| `409` | `preview_expired` | The quote expired, is unknown, or the plan changed since. Preview again. |
| `409` | `device_busy` | A session is running on the phone. |
| `409` | `device_releasing` | The phone is already being removed. |
| `409` | `last_phone` | The plan's last phone. Cancel the plan instead. |
| `409` | `billing_conflict` | The plan needs attention from support. |
| `404` | `device_not_found` | No Lusor Phone on this workspace's plan has that id. |

Nothing is charged or removed when a call returns one of these errors.

## Wipe-and-reset progress

A Lusor Phone can be wiped back to a fresh state from its settings in the app. The API can follow the progress but cannot start a wipe.

```bash theme={null}
curl https://api.lusor.ai/v1/workspaces/$WS/devices/$DEVICE/reset \
  -H "X-Api-Key: $LUSOR_API_KEY"
```

| Field | Meaning |
| - | - |
| `state` | `idle` (no wipe has run), `running`, `succeeded`, `refused` (it did not start, see `reason`) or `failed`. |
| `reason` | Why it was refused or failed. |
| `attempt` | Which wipe this is. |
| `internet` | How the phone's Internet connection was restored afterwards: `status` is `bound`, `needs_proxy_reassign`, `held` or `failed`. `null` before a wipe has run. |
| `requested_at`, `finished_at` | Timestamps. |

A phone that is not a Lusor Phone returns `409 not_managed_phone`.

## Only in the app

These changes are made by someone signed in to the [Lusor app](https://app.lusor.ai). They are not part of the API:

* Starting a Phone plan, and cancelling it.
* Adding or removing your own iPhone.
* Wiping and resetting a Lusor Phone (admins only).
* Renaming a phone, or moving it to another workspace.
* Changing a Lusor Phone's Internet location or static IP. See [Internet and static IP](https://docs.lusor.ai/phones/internet-and-static-ip.md).


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