# Build a multi-tenant product

Source: https://phonebox.dev/docs/guides/multi-tenant

> Give each of your end users their own phone, tag phones with metadata, limit keys to one customer's phones, and park phones between uses.



When your product runs agents on phones for your own customers, give each end user their own phone. Their apps, files and signed-in accounts then stay apart, and one customer's agent never acts on a screen another one is using. This guide shows how to organize the phones, find them again, and keep them from costing money while nobody uses them.

## One phone for each end user [#one-phone-for-each-end-user]

A phone is a persistent Android device, and what an end user sets up on it stays there: the apps you installed for them, the accounts they signed in to, the files they left. Treat the phone as theirs.

* Create a user's phone the first time they need one, and keep using it.
* Park it when their task ends. A parked phone keeps everything and costs nothing.
* Run one agent on a phone at a time. Phonebox doesn't lock phones, so two agents on one phone act on screens they didn't read.

## Tag phones with metadata [#tag-phones-with-metadata]

Give each phone up to 20 metadata pairs of your own, such as the customer's ID in your system, when you create it:

```json title="POST /v1/phones"
{
  "name": "customer-acme",
  "metadata": { "customer": "acme", "plan": "pro" }
}
```

Find a customer's phones with the metadata filter, one pair at a time:

```bash
curl -g "https://phonebox.dev/v1/phones?metadata[customer]=acme" \
  -H "Authorization: Bearer $PHONEBOX_API_KEY"
```

`PATCH /v1/phones/{id}` changes a phone's metadata later, and replaces all of it, so send every pair you want to keep. Metadata keys have up to 40 characters and values up to 256. Use your own IDs as values, not names or email addresses: Phonebox keeps metadata values out of activity records and analytics, but every key that can see the phone can read them.

## Find or create a customer's phone [#find-or-create-a-customers-phone]

This TypeScript function returns a customer's phone, ready to use. It finds the phone by metadata, creates it on first use, and starts it when it is parked:

```ts
import { Phonebox } from "phonebox";

const pb = new Phonebox(); // reads PHONEBOX_API_KEY

export async function phoneFor(customer: string) {
  // The list leaves out failed and deleted phones, which can't be used again.
  let [phone] = (await pb.phones.list({ metadata: { customer } })).data;
  if (!phone) {
    // The same key for every attempt at this customer's next phone. It changes only once that phone is gone,
    // failed or deleted, because the old key would only return the gone phone for 24 hours.
    const failed = await pb.phones.list({ metadata: { customer }, status: "failed", limit: 100 });
    const deleted = await pb.phones.list({ metadata: { customer }, status: "deleted", limit: 100 });
    const idempotencyKey = `customer-${customer}-phone-${failed.data.length + deleted.data.length + 1}`;
    phone = await pb.phones.create({ name: `customer-${customer}`, metadata: { customer }, idempotencyKey });
  }
  if (phone.status === "parking") await phone.waitUntil("parked");
  // A phone on its way is waited on by reading it; start() would renew its session.
  if (phone.status === "creating" || phone.status === "starting") await phone.waitUntil("ready");
  if (phone.status !== "ready") await phone.start();
  return phone;
}
```

* The list leaves out failed and deleted phones, so two more lists, filtered to `failed` and to `deleted`, count the customer's gone phones for the key. A failed phone doesn't count toward your phone limit, so it can stay.
* `create()` waits until the phone is ready, for up to 10 minutes, and returns the phone even when that wait is cut short, so the function checks the status afterwards. Only an error that names the phone in `details.phone` throws a `PhoneboxError`: the phone's own failure, or someone else parking or deleting it while `create()` waits.
* A phone that is still `creating` or `starting` is waited on with `waitUntil("ready")`, which only reads it. `start()` is only for a phone that isn't running: on a running phone it would renew the session. It waits for up to 5 minutes, and throws a `PhoneboxError` when the phone fails to start, or with the code `wait_timeout` when time runs out.

The idempotency key comes from the customer and the number of their phones that are gone, failed or deleted, so it stays the same for every attempt at the same phone and changes once that phone is gone. A key that stayed after a deletion would only return the deleted phone, and the customer would get no phone for 24 hours. Without a key, a create whose reply was lost can't be told apart from one that failed, and a retry can create, and bill, a second phone. With this one, a retry of `phoneFor` after a create that timed out either finds the new phone in the list or sends the same key and gets that phone back. Keys are remembered for 24 hours. They allow only letters, digits, `_`, `.`, `:` and `-`, so build them from IDs of yours that use only those.

Two workers that call `phoneFor` for the same customer at the same moment send the same key and get one phone between them, because Phonebox checks the key and creates the phone in one step. They then share that phone, and Phonebox doesn't lock a phone, so run one customer's work at a time, for example through a queue for each customer.

## Limit keys to a customer's phones [#limit-keys-to-a-customers-phones]

A key can be limited to between 1 and 20 phones when you create it in the console. Such a key:

* sees only those phones: in lists, where it gets all of them on one page, and in the account's phone counts;
* can use only those phones, and any other phone answers `403 phone_not_allowed`;
* can never create phones, whatever its preset;
* sees and uses only the [uploads](/docs/api-reference/uploads) it created, so one customer's builds never reach another's worker. The project's other keys see every upload.

Give a worker that acts for one customer a key limited to that customer's phones, and keep the key that creates phones on a server you trust. A key's phones are fixed when you create the key, and each phone must exist by then. When a customer gets a new phone, create a new key for them and revoke the old one. Keys are created only in the console, never through the API, so plan for a person to create them.

## Park between uses [#park-between-uses]

Parking is what keeps a multi-tenant product affordable:

* Park a customer's phone as soon as their task ends, in a `finally` block, so an error doesn't leave it running.
* Set an `idle_timeout` that suits your tasks as a backstop. A phone parks itself that long after the last request that addressed it, or up to about 15 seconds sooner.
* When the customer comes back, `start()` resumes the phone with everything they left on it. A resume takes a while, so start the phone as soon as you know the customer needs it.

[Keep costs down](/docs/guides/keep-costs-down) has more on timers and spending limits.

## Limits for many customers [#limits-for-many-customers]

A project holds 10 phones by default, not counting deleted or failed ones, and runs 2 at once. With one phone for each customer, that is 10 customers and 2 active at the same time. The running limit counts all of your projects together, so a second project doesn't add to it. Email [team@phonebox.dev](mailto:team@phonebox.dev) with your project ID when you need more, and see [Limits](/docs/billing/limits) for the rest.

## Costs for each customer [#costs-for-each-customer]

Every session belongs to one phone, so a customer's cost is the sum of their phones' sessions. `GET /v1/phones/{id}/sessions` lists each session with its `cost_usd`, and the metadata filter finds the customer's phones. Sessions stay listed after a phone is deleted.
