# macOS Sandboxes

macOS sandboxes are **macOS virtual machines (VMs)** on dedicated Apple silicon Macs. Each sandbox runs on a Mac that your organization reserves, and nobody else uses that Mac while you hold it. You can run commands in a macOS sandbox, connect with SSH, see and control its desktop, and keep its disk between sessions.

A Mac is reserved for a 24-hour period, and the reservation decides how the Mac is divided: one sandbox uses the whole Mac, or two sandboxes share it. macOS sandboxes run only on reservations. When your organization holds no reservation, Daytona can buy one for you when you create a sandbox.

| **Term**         | **Description**                                                                                              |
| ---------------- | ------------------------------------------------------------------------------------------------------------ |
| **Reservation**  | One dedicated Mac for your organization for a 24-hour period. Nobody else uses that Mac while you hold it.  |
| **Layout**       | How a reservation divides its Mac: **`split`** runs up to two sandboxes at a time, **`whole`** runs one.     |
| **Model**        | The type of Mac, for example **`m4pro-12c-24g`**.                                                            |
| **Auto-reserve** | An organization setting that buys a Mac for you when you create a macOS sandbox and hold no reservation.    |
| **Image**        | A macOS disk that a sandbox starts from, for example **`base-macos`** or a snapshot of your sandbox.         |
| **Persistent**   | A sandbox that keeps its disk across stop and start. Stop, start, pause, resume and snapshots need it.       |

## Use cases

macOS sandboxes are used when the software, the build, or the task requires macOS or Apple tooling.

- **Build and test Apple apps**: an agent or a CI job builds and tests iOS and macOS apps with Xcode and the iPhone simulator on a clean Mac.
- **Computer-use agents on macOS**: an agent operates macOS applications through screenshots and mouse and keyboard input.
- **Long-lived build machines**: a persistent sandbox keeps its disk across stop and start, so installed tools and build caches stay between sessions.
- **Fleets of Macs**: one request reserves up to 100 Macs for parallel builds and tests.

## Requirements

Your organization must have macOS sandboxes turned on. Without it, the API answers **`403 FEATURE_NOT_ENABLED`** when you reserve a Mac or create a macOS sandbox. Sign in to the dashboard at [app.daytona.com ↗](https://app.daytona.com) and create a project API key. The examples read it from `DAYTONA_API_KEY`.

### Endpoints

macOS sandboxes have their own API address. Send all macOS requests to the macOS API.

| **What**                                  | **Address**                                     |
| ----------------------------------------- | ----------------------------------------------- |
| Dashboard                                 | **`https://app.daytona.com`**                   |
| Daytona API (organizations, API keys)     | **`https://api.daytona.com`**                   |
| macOS API (reservations, macOS sandboxes) | **`https://api.us-macos.daytona.com`**          |
| Sandbox SSH                               | **`ssh.us-macos.daytona.com`**, port **`2222`** |
| Region of the Macs                        | **`macos-1`**                                   |

The SDKs and the CLI read the API address and your key from environment variables. Give the region `macos-1` on each reservation and each macOS create.

```bash
export DAYTONA_API_KEY=dtn_...
export DAYTONA_API_URL=https://api.us-macos.daytona.com
export DAYTONA_REGION=macos-1    # the CLI's default region
```

### Install

Pin these exact versions. Do not use `pip install --pre daytona` or `npm install @daytona/sdk@latest`: they install other versions that do not have the macOS features.


Python 3.10 or later:

```bash
pip install daytona==2.0.0a2
```


Node.js 22.4 or later:

```bash
npm install @daytona/sdk@2.0.0-alpha.2
```


On macOS or Linux:

```bash
curl -fsSL https://github.com/daytona/clients/releases/download/v2.0.0-alpha.2/install.sh | sh -s -- --version 2.0.0-alpha.2
```

With `DAYTONA_API_KEY` set, the CLI needs no `daytona login`.


### Connect

The SDK clients read your key and the macOS API address from the environment.


```python
from daytona import Daytona

daytona = Daytona()  # reads DAYTONA_API_KEY and DAYTONA_API_URL
```

`AsyncDaytona` has the same methods with `await`.


```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona() // reads DAYTONA_API_KEY and DAYTONA_API_URL
```


:::note[Units]
- In the SDKs, **`memory`** and **`disk`** are in GiB. In the CLI, **`--memory`** and **`--disk`** are in MiB.
- Python timeouts are in seconds.
- TypeScript timeouts of **`exec`**, **`start`**, **`pause`**, **`resume`** and process handles are in milliseconds. **`waitSeconds`**, **`createTimeoutSeconds`** and the **`timeout`** of **`sandbox.wait`** are in seconds.
:::

## Mac reservations

A reservation gives your organization one dedicated Mac for a 24-hour period. Nobody else uses that Mac while you hold it. You pay for each period when it starts. The first period starts when a Mac accepts the reservation, not when you send the request, and time spent waiting for a Mac is not charged.

You cannot end a reservation early. It ends at the end of its period unless it renews. A reservation has one layout, which you choose when you reserve, and you cannot change it. Both layouts cost the same. macOS sandboxes run only on your reservations: a create picks a reservation with free room, or you name one.

| **Layout**  | **Description**                                    |
| ----------- | -------------------------------------------------- |
| **`split`** | Up to two sandboxes run on the Mac at the same time. |
| **`whole`** | One sandbox runs on the Mac.                       |

<Collapsible title="Reservation statuses">

| **Status**           | **Description**                                                                                                                                       |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`pending_charge`** | We are charging the period.                                                                                                                           |
| **`pending_mac`**    | The period is paid. The reservation waits for a free Mac. If no Mac accepts it within 5 minutes, it becomes **`failed`** and we refund the charge.     |
| **`active`**         | A Mac holds the reservation. **`period_end`** tells you when the period ends.                                                                         |
| **`ending`**         | The period ended. We stop the sandboxes, save persistent disks and erase the Mac. See [when a reservation ends](#when-a-reservation-ends).           |
| **`released`**       | The reservation ended. **`end_reason`** tells you why, for example **`expired`**.                                                                     |
| **`failed`**         | The charge failed, or no Mac accepted the reservation (refunded).                                                                                     |

</Collapsible>

- Useful reservation fields: **`id`**, **`status`**, **`model`**, **`layout`**, **`period_end`**, **`auto_renew`**, **`renewal_error`**, **`capacity`** (the Mac's CPU, memory and sandbox disk), **`resources_used`**, **`slots_used`** and **`sandboxes_per_mac`**.

### Models and prices

The production Macs are **Apple M4 Pro, 12 cores, 24 GB memory**. Their model ID is `m4pro-12c-24g`. Always give this model when you reserve. The price of one `m4pro-12c-24g` reservation is **$2.5032 per Mac-hour, $60.08 per 24-hour period**.

| **Layout**            | **Sandboxes at a time** | **Default size of each sandbox**     |
| --------------------- | ----------------------- | ------------------------------------ |
| **`split`** (default) | 2                       | 6 vCPU, 12 GiB memory, 110 GiB disk  |
| **`whole`**           | 1                       | 12 vCPU, 24 GiB memory, 220 GiB disk |

Read the current price and the number of free Macs from the Mac catalog (`GET /mac-catalog`). The number of free Macs is a guide only: another purchase can take a free Mac before yours.


```python
catalog = daytona.v2.mac_models.catalog(region="macos-1")
for region in catalog.regions:
    for entry in region.models:
        price = entry.price
        print(
            entry.model.id,
            "free:", entry.available,
            "per period: $%.2f" % (price.period_cents / 100) if price else "no price",
        )
        for layout in entry.model.layouts:
            print("  ", layout.layout, layout.max_sandboxes, "sandboxes,",
                  layout.default_vcpus, "vCPU,", layout.default_memory_mib, "MiB,",
                  layout.default_disk_mib, "MiB disk")
```

- **`available`** is the number of free Macs.
- **`daytona.v2.mac_models.list()`** lists the models without prices.


```typescript
const catalog = await daytona.v2.macModels.catalog({ region: 'macos-1' })
for (const region of catalog.regions) {
  for (const entry of region.models) {
    const perPeriod = entry.price ? `$${(entry.price.period_cents / 100).toFixed(2)}` : 'no price'
    console.log(entry.model.id, 'free:', entry.available, 'per period:', perPeriod)
  }
}
```

- **`available`** is the number of free Macs.
- **`daytona.v2.macModels.list()`** lists the models without prices.


```bash
daytona mac-model list --region macos-1
```


### Reserve a Mac

Reserve one Mac of a model in a region, with the layout you choose. Each purchase has an idempotency key. If you retry with the same key, you get the same reservation and you do not pay again. If you do not give a key, the SDK and the CLI make a new key for each call, so a retry buys a second Mac. Give your own key when your code can retry.


```python
reservation = daytona.v2.reservations.create(
    region="macos-1",
    model="m4pro-12c-24g",
    layout="split",                    # or "whole"
    idempotency_key="build-mac-2026-10-24",
)
print(reservation.id, reservation.status)
```


```typescript
const reservation = await daytona.v2.reservations.create({
  region: 'macos-1',
  model: 'm4pro-12c-24g',
  layout: 'split', // or 'whole'
  idempotencyKey: 'build-mac-2026-10-24',
})
console.log(reservation.id, reservation.status)
```


```bash
daytona reservation create --region macos-1 --model m4pro-12c-24g --layout split \
  --idempotency-key build-mac-2026-10-24
```


- **`auto_renew`** (**`autoRenew`**, **`--auto-renew`**) sets [automatic renewal](#automatic-renewal). If you do not set it, it follows your organization's auto-reserve setting, which is on by default.
- If no Mac of the model is free, the request fails with **`409 NO_MAC_AVAILABLE`** (**`ConflictError`** in the SDKs) and you pay nothing.

### List and get reservations

List the reservations of your organization, or get one by its ID. The list returns the open reservations and the reservations that ended in the last 30 days.


```python
for r in daytona.v2.reservations.iter():  # every page
    print(r.id, r.status, r.layout, r.period_end, r.auto_renew)
r = daytona.v2.reservations.get(reservation.id)
```


```typescript
const page = await daytona.v2.reservations.list() // one page; reservations.iter() reads them all
const one = await daytona.v2.reservations.get(reservation.id)
```


```bash
daytona reservation list
daytona reservation get RESERVATION_ID
```


### Reserve many Macs

Reserve 1 to 100 Macs of one model in one request. Each Mac is a separate reservation with its own ID, period, charge and renewal.

By default, the request is all or nothing: if fewer Macs are free than you ask for, it reserves none and fails with `409 NO_MAC_AVAILABLE`. Set a minimum count to accept fewer: the request then reserves as many as are free, if that is at least the minimum.


```python
batch = daytona.v2.reservations.create_bulk(
    region="macos-1",
    model="m4pro-12c-24g",
    count=20,
    min_count=10,
    layout="whole",
    idempotency_key="ci-fleet-2026-10-24",
)
print(f"reserved {batch.reserved} of {batch.requested} Macs")
ids = [r.id for r in batch.reservations]
```


```typescript
const batch = await daytona.v2.reservations.createBulk({
  region: 'macos-1',
  model: 'm4pro-12c-24g',
  count: 20,
  minCount: 10,
  layout: 'whole',
  idempotencyKey: 'ci-fleet-2026-10-24',
})
console.log(`reserved ${batch.reserved} of ${batch.requested} Macs`)
```


`--dry-run` shows the price and the free Macs and reserves nothing.

```bash
daytona reservation create --region macos-1 --model m4pro-12c-24g --count 20 --dry-run
daytona reservation create --region macos-1 --model m4pro-12c-24g --count 20 --min-count 10 \
  --layout whole --idempotency-key ci-fleet-2026-10-24
```


- **`min_count`** (**`minCount`**, **`--min-count`**) sets the minimum count.
- A retry with the same key and the same body returns the same reservations. The same key with a different body fails with **`409 IDEMPOTENCY_KEY_REUSED`**.
- If the charge of one Mac fails, that reservation shows **`failed`** and the others stay. The request fails with **`402`** only when every charge fails.

### Renew a reservation

Renew a reservation to pay for the next 24-hour period on the same Mac. You keep the Mac, its sandboxes and their disks. Use a new key for each period you want to buy, and the same key when you retry.


```python
daytona.v2.reservations.renew(reservation.id, idempotency_key=f"renew-{reservation.id}-2")
```


```typescript
await daytona.v2.reservations.renew(reservation.id, { idempotencyKey: `renew-${reservation.id}-2` })
```


```bash
daytona reservation renew RESERVATION_ID --idempotency-key renew-RESERVATION_ID-2
```


### Automatic renewal

When automatic renewal is on, we renew the reservation in the last hour of its period, **but only while a sandbox on that Mac is active** (being created, starting or running). If all its sandboxes are stopped or paused, or it has none, the reservation does not renew and ends at `period_end`. If a renewal fails, the reservation shows the reason in `renewal_error` and ends at `period_end`.

Turn automatic renewal on or off for one reservation.


```python
daytona.v2.reservations.update(reservation.id, auto_renew=False)
```


```typescript
await daytona.v2.reservations.update(reservation.id, { autoRenew: false })
```


```bash
daytona reservation update RESERVATION_ID --auto-renew=false
```


### Auto-reserve

Auto-reserve buys a Mac for you when you create a macOS sandbox and your organization holds no live reservation in the region. It buys the cheapest model that fits the sandbox, in that model's default layout, with automatic renewal on. If you want to control every purchase, turn auto-reserve off and reserve Macs yourself.

- It is **on by default**. It holds at most **2** reservations at a time. Above that, a create fails with **`409 AUTO_RESERVE_LIMIT_REACHED`**.
- It does not buy a Mac when you have a reservation that is only full (**`NO_FREE_SLOT`** or **`MAC_DISK_FULL`**).
- An organization admin can turn it off, or change the limit, in the dashboard: Settings, **Auto-reserve Macs**.

## Create a macOS sandbox

Create a macOS sandbox on one of your reservations. The base image is `base-macos`: macOS 26.6.2 with Xcode 26.6 and its iPhone simulator, Homebrew (with git, Node.js 24, Ruby, CocoaPods, fastlane, gh and jq) and Python 3.14. The user in the sandbox is `lume`, with home `/Users/lume`.

Without a size, each sandbox gets the default half of a `split` Mac or all of a `whole` Mac. The following examples create two sandboxes on one `split` reservation, each with the default half.


```python
r = daytona.v2.reservations.create(region="macos-1", model="m4pro-12c-24g", layout="split",
                             idempotency_key="split-mac-1")
a = daytona.v2.create_macos("base-macos", region="macos-1", reservation=r.id)
b = daytona.v2.create_macos("base-macos", region="macos-1", reservation=r.id)
```

The following example creates a persistent sandbox that uses a whole Mac:

```python
r = daytona.v2.reservations.create(region="macos-1", model="m4pro-12c-24g", layout="whole",
                             idempotency_key="whole-mac-1")
sandbox = daytona.v2.create_macos(
    "base-macos",
    region="macos-1",
    reservation=r.id,
    cpu=12,
    memory=24,
    disk=220,
    persistent=True,
    name="ios-builder",
    wait_seconds=120,
)
print(sandbox.id, sandbox.reservation_id, sandbox.cpu, sandbox.memory, sandbox.disk)
```

| **Parameter**                | **Type**  | **Description**                                                                                                                       |
| ---------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **`image`**                  | str       | Sets the macOS image that the sandbox starts from, for example **`base-macos`** or a snapshot. This is the first positional argument. |
| **`region`**                 | str       | Sets the region, **`macos-1`**.                                                                                                       |
| **`reservation`**            | str       | Sets the reservation that the sandbox runs on: a reservation ID, or **`"new"`**. See [which reservation](#which-reservation).         |
| **`auto_renew`**             | bool      | Sets [automatic renewal](#automatic-renewal) of the reservation that the create buys. Only with **`reservation="new"`**.              |
| **`cpu`**                    | int       | Sets whole vCPUs. See [sizes](#sizes).                                                                                                |
| **`memory`**                 | float     | Sets the memory in GiB. See [sizes](#sizes).                                                                                          |
| **`disk`**                   | float     | Sets the whole guest disk in GiB, macOS included. See [sizes](#sizes).                                                                |
| **`persistent`**             | bool      | Keeps the disk across stop and start. See [filesystem persistence](#filesystem-persistence).                                          |
| **`name`**                   | str       | Sets the display name.                                                                                                                |
| **`wait_seconds`**           | int       | Sets how many seconds the call waits for the sandbox to start.                                                                        |
| **`create_timeout_seconds`** | int       | Sets how many seconds the server tries before it deletes the waiting sandbox.                                                         |


```typescript
const r = await daytona.v2.reservations.create({
  region: 'macos-1', model: 'm4pro-12c-24g', layout: 'split', idempotencyKey: 'split-mac-1',
})
const a = await daytona.v2.createMacos('base-macos', { region: 'macos-1', reservation: r.id })
const b = await daytona.v2.createMacos('base-macos', { region: 'macos-1', reservation: r.id })
```

The following example creates a persistent sandbox that uses a whole Mac:

```typescript
const r = await daytona.v2.reservations.create({
  region: 'macos-1', model: 'm4pro-12c-24g', layout: 'whole', idempotencyKey: 'whole-mac-1',
})
const sandbox = await daytona.v2.createMacos('base-macos', {
  region: 'macos-1',
  reservation: r.id,
  cpu: 12,
  memory: 24,
  disk: 220,
  persistent: true,
  name: 'ios-builder',
  waitSeconds: 120,
})
console.log(sandbox.id, sandbox.reservationId)
```

| **Parameter**              | **Type** | **Description**                                                                                                                       |
| -------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **`image`**                | string   | Sets the macOS image that the sandbox starts from, for example **`base-macos`** or a snapshot. This is the first positional argument. |
| **`region`**               | string   | Sets the region, **`macos-1`**.                                                                                                       |
| **`reservation`**          | string   | Sets the reservation that the sandbox runs on: a reservation ID, or **`'new'`**. See [which reservation](#which-reservation).         |
| **`autoRenew`**            | boolean  | Sets [automatic renewal](#automatic-renewal) of the reservation that the create buys. Only with **`reservation: 'new'`**.             |
| **`cpu`**                  | number   | Sets whole vCPUs. See [sizes](#sizes).                                                                                                |
| **`memory`**               | number   | Sets the memory in GiB. See [sizes](#sizes).                                                                                          |
| **`disk`**                 | number   | Sets the whole guest disk in GiB, macOS included. See [sizes](#sizes).                                                                |
| **`persistent`**           | boolean  | Keeps the disk across stop and start. See [filesystem persistence](#filesystem-persistence).                                          |
| **`name`**                 | string   | Sets the display name.                                                                                                                |
| **`waitSeconds`**          | number   | Sets how many seconds the call waits for the sandbox to start.                                                                        |
| **`createTimeoutSeconds`** | number   | Sets how many seconds the server tries before it deletes the waiting sandbox.                                                         |


Run the command two times to create two sandboxes on one `split` reservation:

{/* snippet-check: skip The daytona CLI hides sandbox create-macos from its help. */}
```bash
daytona sandbox create-macos --region macos-1 --image base-macos --reservation RESERVATION_ID
```

The following example creates a persistent sandbox that uses a whole Mac:

{/* snippet-check: skip The daytona CLI hides sandbox create-macos from its help. */}
```bash
daytona sandbox create-macos --region macos-1 --image base-macos --reservation RESERVATION_ID \
  --vcpus 12 --memory 24576 --disk 225280 --persistent --name ios-builder
```

| **Flag**            | **Type** | **Description**                                                                                                         |
| ------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
| **`--image`**       | string   | Sets the macOS image that the sandbox starts from, for example **`base-macos`** or a snapshot.                          |
| **`--region`**      | string   | Sets the region, **`macos-1`**.                                                                                         |
| **`--reservation`** | string   | Sets the reservation that the sandbox runs on: a reservation ID, or **`new`**. See [which reservation](#which-reservation). |
| **`--auto-renew`**  | bool     | Sets [automatic renewal](#automatic-renewal) of the reservation that the create buys. Only with **`--reservation new`**. |
| **`--vcpus`**       | int32    | Sets whole vCPUs. See [sizes](#sizes).                                                                                  |
| **`--memory`**      | int32    | Sets the memory in MiB. See [sizes](#sizes).                                                                            |
| **`--disk`**        | int32    | Sets the whole guest disk in MiB, macOS included. See [sizes](#sizes).                                                  |
| **`--persistent`**  | bool     | Keeps the disk across stop and start. See [filesystem persistence](#filesystem-persistence).                            |
| **`--name`**        | string   | Sets the display name.                                                                                                  |
| **`--wait`**        | int      | Sets how many seconds the server holds the create request before it returns.                                           |
| **`--create-ttl`**  | int      | Sets how many seconds the server tries before it deletes the waiting sandbox.                                           |


- Other create options work as on [Linux sandboxes](https://www.daytona.io/docs/en/v2/linux-sandboxes.md#create-a-linux-sandbox): **`env`**, **`labels`**, **`secrets`**, **`ports`** and **`network_policy`** (**`networkPolicy`**, **`--block-all`**, **`--allow-domain`**, **`--allow-cidr`**).

### Sizes

A macOS sandbox takes its CPU, memory and disk from the Mac of its reservation. The disk is the whole guest disk, macOS included. On `m4pro-12c-24g`, the sandboxes of one Mac share 12 vCPU, 24 GiB memory and 220 GiB disk. A Mac fixes the size when it accepts the sandbox, and `sandbox.cpu`, `sandbox.memory` and `sandbox.disk` show it from then on.

| **Setting** | **SDK (GiB)**           | **CLI (MiB)**     | **Minimum**        | **If you omit it**                                                    |
| ----------- | ----------------------- | ----------------- | ------------------ | --------------------------------------------------------------------- |
| CPU         | **`cpu`** (whole vCPUs) | **`--vcpus`**     | 4                  | Half of the Mac (**`split`**), all of it (**`whole`**)                |
| Memory      | **`memory`**            | **`--memory`**    | 4 GiB (4096 MiB)   | Half of the Mac (**`split`**), all of it (**`whole`**)                |
| Disk        | **`disk`**              | **`--disk`**      | 80 GiB (81920 MiB) | Half of the Mac's sandbox disk (**`split`**), all of it (**`whole`**) |

### Which reservation

A create places the sandbox on a reservation. The reservation option decides which one, and whether the create buys a new one.

- **Omit** **`reservation`**: the sandbox goes on any of your reservations in the region that has room. If you have none, [auto-reserve](#auto-reserve) buys one (if it is on).
- **`reservation="<id>"`**: the sandbox goes on that reservation.
- **`reservation="new"`**: the create always buys a new reservation for this sandbox. Only here can you also set **`auto_renew`**.

The create can wait for room on a reservation. `wait_seconds` (`waitSeconds`, `--wait`) is how long the call waits for the sandbox to start. `create_timeout_seconds` (`createTimeoutSeconds`, `--create-ttl`) is how long the server tries before it deletes the waiting sandbox. Both are in seconds. If that time runs out, a reservation that the create bought stays bought.

### Get a sandbox

Get a macOS sandbox by its ID, or list the sandboxes in the macOS region.


```python
sandbox = daytona.v2.get("SANDBOX_ID")
```


```typescript
const sandbox = await daytona.v2.get('SANDBOX_ID')
```


```bash
daytona sandbox get SANDBOX_ID
daytona sandbox list --region macos-1
```


## Run commands

Run commands in a macOS sandbox, in the foreground or in the background. Files (`sandbox.fs`), git (`sandbox.git`) and PTYs (`sandbox.pty`) work as on Linux sandboxes.


```python
result = sandbox.exec("sw_vers -productVersion && xcodebuild -version")
print(result.exit_code, result.output)

handle = sandbox.process.start("xcodebuild -scheme App build")  # runs in the background
print(handle.wait().exit_code)
```


```typescript
const result = await sandbox.exec('sw_vers -productVersion && xcodebuild -version')
console.log(result.exitCode, result.output)
```


```bash
daytona sandbox exec SANDBOX_ID -- sw_vers -productVersion
daytona sandbox exec SANDBOX_ID --tty -- zsh        # an interactive shell
daytona sandbox cp ./App.zip SANDBOX_ID:/Users/lume/App.zip
```


## SSH

Connect to a macOS sandbox over SSH. The CLI opens SSH for you. With your own `ssh` client, make a short-lived sandbox token that allows SSH, and put the sandbox ID and the token in the user name, joined by `#`.

The gateway checks the token, not a password or an SSH key. A token works until it expires. Make a new one when you need it.


```python
token = sandbox.create_access_token(operations=["ssh"], ttl_seconds=3600)
print(f'ssh -p 2222 "{sandbox.id}#{token.access_token}@ssh.us-macos.daytona.com"')
```


```typescript
const token = await sandbox.createAccessToken({ operations: ['ssh'], ttlSeconds: 3600 })
console.log(`ssh -p 2222 "${sandbox.id}#${token.accessToken}@ssh.us-macos.daytona.com"`)
```


```bash
daytona sandbox ssh SANDBOX_ID
daytona sandbox ssh SANDBOX_ID -- ls /Users/lume
```


:::caution
Do not put a project API key in an SSH command. Use a sandbox token: it is limited to this sandbox and to SSH.
:::

## Desktop

macOS sandboxes have a desktop of 1280 × 800. Open the sandbox in the dashboard and choose the desktop view. The desktop takes mouse and keyboard input as soon as it connects.

### Computer use

Take screenshots and send mouse and keyboard input inside the sandbox with `sandbox.computer_use` (Python) and `sandbox.computerUse` (TypeScript). `cu.a11y` reads the accessibility tree and acts on controls, and `cu.recording` records the screen.


```python
import base64

cu = sandbox.computer_use
cu.start()
print(cu.status())

shot = cu.screen.capture()
with open("screen.png", "wb") as f:
    f.write(base64.b64decode(shot.screenshot))

cu.mouse.click(x=640, y=400)
cu.keyboard.type(text="hello from Daytona")
cu.keyboard.hotkey(keys="cmd+space")       # modifiers: cmd, ctrl, alt (option), shift
print(cu.display.windows())
```


```typescript
const cu = sandbox.computerUse
await cu.start()
const shot = await cu.screen.capture()       // shot.screenshot is a base64 PNG
await cu.mouse.click({ x: 640, y: 400 })
await cu.keyboard.type({ text: 'hello from Daytona' })
await cu.keyboard.hotkey({ keys: 'cmd+space' })
```


### VNC client

Connect your own VNC client to the desktop. The desktop is also a VNC connection over a WebSocket. Make a token with the `desktop` operation and connect a noVNC client to the address below. The proxy does the VNC password step for you. Your client must support the DesktopSize encoding (noVNC does).

```text
wss://proxy.us-macos.daytona.app/vnc/SANDBOX_ID?rlp_token=TOKEN
```

```python
token = sandbox.create_access_token(operations=["desktop"], ttl_seconds=3600)
```

## Logs

A macOS sandbox has a console log, and your own processes can keep their own logs. The console log is erased with the Mac when the reservation ends. Copy what you need before then.

| **Log**                   | **Description**                                                                                                                                                                       |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Console log**           | The macOS sandbox's console output and our lifecycle lines. The API route is **`GET /sandboxes/SANDBOX_ID/console-logs`**.                                                            |
| **Your own process logs** | Run a command with **`process.start(...)`** and read **`handle.logs()`** or **`handle.stream_logs()`** (TypeScript: **`logs()`**, **`streamLogs()`**), or write logs to a file and copy it out. |
| **Usage**                 | **`daytona sandbox usage SANDBOX_ID`**.                                                                                                                                               |

```bash
daytona sandbox logs SANDBOX_ID
daytona sandbox logs SANDBOX_ID --follow
```

- macOS images have no entrypoint, so **`entrypoint_logs()`** (**`entrypointLogs()`**, **`logs --entrypoint`**) returns **`(no entrypoint output)`**.

## Lifecycle

Every macOS sandbox moves through a lifecycle. Stop, start, pause and resume need a **persistent** sandbox (`persistent=True`). A sandbox that is not persistent can only run or be deleted.

If a Mac restarts, a running persistent sandbox comes back stopped with its disk. A running sandbox that is not persistent is lost.

| **Action** | **What happens**                                                                                                                                                                      |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Stop       | macOS shuts down. The disk stays on the same Mac and keeps its disk room on the reservation. The CPU, memory and slot are free for other sandboxes.                                   |
| Start      | macOS boots from the same disk on the same Mac. The reservation needs a free slot, CPU and memory for the sandbox, or start fails with **`NO_FREE_SLOT`**. Start never buys a reservation. |
| Pause      | The Mac saves the memory of the sandbox. It keeps its place on the reservation.                                                                                                       |
| Resume     | The sandbox continues from the saved memory. If the memory cannot be restored, macOS boots from the disk and **`mac_boot_mode`** is **`cold_after_restore_failure`**.                 |
| Delete     | The sandbox and its disk are erased. You cannot undo this.                                                                                                                            |

:::caution
A stopped or paused sandbox **does not keep automatic renewal going**. If all sandboxes on a reservation are stopped or paused at the end of the period, the reservation ends. See [when a reservation ends](#when-a-reservation-ends).
:::

### Stop / start

Stop a persistent macOS sandbox to shut down macOS and keep its disk on the same Mac. Start boots macOS from the same disk on the same Mac.


```python
sandbox.stop()
sandbox.start(timeout=120)
```


```typescript
await sandbox.stop()
await sandbox.start({ timeout: 120_000 })
```


```bash
daytona sandbox stop SANDBOX_ID
daytona sandbox start SANDBOX_ID
```


### Pause / resume

Pause a persistent macOS sandbox to save its memory on the Mac. Resume continues the sandbox from the saved memory.


```python
sandbox.pause()
sandbox.resume()
```


```typescript
await sandbox.pause()
await sandbox.resume()
```


```bash
daytona sandbox pause SANDBOX_ID
daytona sandbox resume SANDBOX_ID
```


### Fork

Fork a persistent macOS sandbox to copy its disk into a new sandbox. A fork does not copy memory: `memory=True` fails with `NOT_SUPPORTED_FOR_CLASS`.


```python
twin = sandbox.fork(name="ios-builder-2")
```


```typescript
const twin = await sandbox.fork({ name: 'ios-builder-2' })
```


```bash
daytona sandbox fork SANDBOX_ID --name ios-builder-2
```


### Delete

Delete a macOS sandbox to erase the sandbox and its disk. You cannot undo this.


```python
sandbox.delete()
```


```typescript
await sandbox.delete()
```


```bash
daytona sandbox delete SANDBOX_ID
```


## Filesystem persistence

A persistent macOS sandbox keeps its disk on its Mac across stop and start for as long as the reservation lives. To keep the disk after the reservation, make a [filesystem snapshot](#filesystem-snapshot), or let the end of the reservation save it (see [when a reservation ends](#when-a-reservation-ends)).

## Filesystem snapshot

A filesystem snapshot saves the disk of a stopped persistent macOS sandbox as an image. You can create new sandboxes from it on any Mac of the region. Snapshots save the disk only, not the memory.

A new sandbox from an image gets a new machine identity. Apps that are bound to one machine (for example an iCloud sign-in or some licences) need you to sign in again.


```python
sandbox.stop()
image = sandbox.snapshot_filesystem(name="ios-builder-ready")
image = daytona.v2.images.get(image.id, wait="ready", timeout=3600)
print(image.id, image.status)

copy = daytona.v2.create_macos("ios-builder-ready", region="macos-1", persistent=True)
```


```typescript
await sandbox.stop()
const image = await sandbox.snapshotFilesystem({ name: 'ios-builder-ready' })
const ready = await daytona.v2.images.get(image.id, { wait: 'ready', timeout: 3_600_000 })
const copy = await daytona.v2.createMacos('ios-builder-ready', { region: 'macos-1', persistent: true })
```


{/* snippet-check: skip The daytona CLI hides sandbox create-macos from its help. */}
```bash
daytona sandbox stop SANDBOX_ID
daytona image capture SANDBOX_ID --name ios-builder-ready
daytona image list --name ios-builder-ready
daytona sandbox create-macos --region macos-1 --image ios-builder-ready --persistent
```


- The sandbox must be persistent and stopped, and its reservation must be active.
- The sandbox cannot start until the snapshot is **`ready`**.
- A snapshot with a name is kept until you delete it. A snapshot without a name is deleted when no sandbox is created from it for 7 days.

## When a reservation ends

A reservation ends at `period_end` when it does not renew. There is no grace period, and we do not send a notice. Watch `period_end`, `auto_renew` and `renewal_error` on your reservations, or [renew by hand](#renew-a-reservation).

At the end, in this order:

1. The reservation becomes **`ending`**. You cannot start, resume or create sandboxes on it.
2. **Persistent sandboxes** are shut down (paused ones are resumed first, then shut down). Each one that ran is **exported as an image**: an image without a name that records the sandbox's ID.
3. **Sandboxes that are not persistent** are deleted. Their data is lost.
4. When every export is saved or has failed, the Mac is **erased**: the sandbox disks, the console logs, the macOS user and its files. A failed export holds the erase for up to 24 hours. The disk of that sandbox is lost.
5. The Mac is checked and goes back to the pool for the next customer.
6. The reservation becomes **`released`**. Every sandbox on it is deleted.

### What you keep

| **Data**                          | **After the end**                                                                                                                                  |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Disk of a persistent sandbox      | Kept as an image without a name, unless its export fails. It is deleted when no sandbox is created from it for 30 days. Give it a name to keep it. |
| Snapshots you made                | Kept. Named ones until you delete them. Unnamed ones while a sandbox is created from them at least once every 7 days.                              |
| Usage records and bills           | Kept.                                                                                                                                              |
| Sandboxes that are not persistent | Lost.                                                                                                                                              |
| Memory of paused sandboxes        | Lost. The disk is exported.                                                                                                                        |
| Console logs                      | Lost.                                                                                                                                              |
| The Mac                           | Erased.                                                                                                                                            |

### Continue after the end

Find the exported image, then create a sandbox from it on a reservation. You can also give the image a name to keep it. The new sandbox needs a reservation with room for the disk: an existing one, a new one you buy, or one that auto-reserve buys. It gets a new machine identity.


```python
exports = daytona.v2.images.list(anonymous=True, sandbox_id="OLD_SANDBOX_ID")
image = exports.items[0]
daytona.v2.images.promote(image.id, "ios-builder-day-2")   # optional: keep it
sandbox = daytona.v2.create_macos(image.id, region="macos-1", persistent=True, wait_seconds=120)
```


```typescript
const exports = await daytona.v2.images.list({ anonymous: true, sandboxId: 'OLD_SANDBOX_ID' })
const image = exports.items[0]
await daytona.v2.images.promote(image.id, 'ios-builder-day-2') // optional: keep it
const sandbox = await daytona.v2.createMacos(image.id, { region: 'macos-1', persistent: true, waitSeconds: 120 })
```


{/* snippet-check: skip The daytona CLI hides sandbox create-macos from its help. */}
```bash
daytona image list --anonymous true --sandbox OLD_SANDBOX_ID
daytona image promote IMAGE_ID ios-builder-day-2
daytona sandbox create-macos --region macos-1 --image IMAGE_ID --persistent
```


## Errors

The SDKs raise these errors as `ConflictError` (409), `ForbiddenError` (403, TypeScript `AuthorizationError`) or `DaytonaError` (402). Read `error.code`. If something does not work as this page says, send us the sandbox ID or the reservation ID and the time. We read the server logs from those.

| **Code**                         | **Meaning**                                                                                                                                             | **What to do**                                                 |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| **`NO_ACTIVE_RESERVATION`**      | You have no live reservation in the region, and auto-reserve is off. Resume also returns it when the reservation of the paused sandbox is **`ending`**. | Reserve a Mac, or use **`reservation="new"`**.                 |
| **`NO_FREE_SLOT`**               | Your reservations have no room for this size.                                                                                                           | Use a smaller size, delete a sandbox, or reserve another Mac.  |
| **`MAC_DISK_FULL`**              | The Macs of the reservations with room for this size have too little free disk space. A smaller **`disk`** does not help.                              | Delete a macOS sandbox on one of them, or reserve another Mac. |
| **`NO_MAC_AVAILABLE`**           | No Mac of the model is free to buy.                                                                                                                     | Try again later, or reserve fewer (**`min_count`**).           |
| **`AUTO_RESERVE_LIMIT_REACHED`** | Auto-reserve holds its maximum number of reservations.                                                                                                  | Reserve a Mac yourself, or ask an admin to raise the limit.    |
| **`IDEMPOTENCY_KEY_REUSED`**     | The key was used for a different purchase, or for a sandbox create with a different size.                                                               | Use a new key.                                                 |
| **`INSUFFICIENT_BALANCE`**       | Your balance does not cover the period of each Mac you reserve (402).                                                                                   | Add balance, or reserve fewer Macs.                            |
| **`CHARGE_FAILED`**              | The charge for the period failed (402).                                                                                                                 | Contact us.                                                    |
| **`SNAPSHOT_IN_PROGRESS`**       | A snapshot of this sandbox is in progress.                                                                                                              | Wait until the image is **`ready`**.                           |
| **`NOT_SUPPORTED_FOR_CLASS`**    | The action does not work on macOS, for example a fork with memory.                                                                                      | See the section for that action.                               |
| **`FEATURE_NOT_ENABLED`**        | macOS sandboxes are not on for your organization.                                                                                                       | Contact us.                                                    |