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

# Pause, Resume, and Extend

> Pause a sandbox to stop its compute and keep its files, resume it, extend its lifetime, or let idle shutdown do it for you.

Pausing stops a sandbox's compute and keeps its files. Resuming brings back that sandbox on the
same provider account. Extending adds running time.

**Before You Begin:** `BOXROUTER_API_KEY` set and a client installed, as in the [quickstart](https://boxrouter.ai/docs/quickstart.md).

| You Want | Set at Creation | Section |
| - | - | - |
| Pause now, resume later | `pause_resume` | [Pause and Resume](#pause-and-resume) |
| Run longer than asked | `resources.extendable_lifetime` | [Extend the Lifetime](#extend-the-lifetime) |
| Pause when unused | `requirements.idle_shutdown` | [Idle Shutdown](#idle-shutdown) |
| Pause at the lifetime's end | `at_end: pause` | [Pause When the Lifetime Ends](#pause-when-the-lifetime-ends) |

## Create a Sandbox That Can Pause

Add the `pause_resume` requirement when you create the sandbox. Name it in a list, as in `requirements=["pause_resume"]`,
to accept any route that can pause. BoxRouter records what that route keeps in the sandbox's `contract`. Only routes that can pause
are eligible. If none can, creation fails with `no_eligible_route` and nothing exists to clean up.

Every sandbox has `pause`, `resume` and `extend`, but a call the sandbox did not name at creation is refused with
`not_named`. To extend later, also set `resources.extendable_lifetime` to `true`.

Name `keeps` (`files_only` or `files_and_memory`) only when you need one. BoxRouter
excludes a route that offers a different value than the one you named, even a larger one. To accept both,
pass a list.

On Credits, a sandbox is always destroyed at the end of its lifetime. A request that asks for `pause_resume` or
`at_end: pause` is refused with `409 policy_denied`. To pause, [connect your own provider key](https://boxrouter.ai/docs/provider-keys.md).

## Pause and Resume

This writes a file, pauses the sandbox, resumes it, and reads the file back. The HTTP example needs `curl` and `jq`.

```python Python theme={"theme":"css-variables"}
from boxrouter import BoxRouter

with BoxRouter() as client:
    with client.sandboxes.create(requirements=["pause_resume"], lifetime_seconds=900) as box:
        box.exec("echo saved > /tmp/state.txt")
        print(box.pause().state)
        print(box.resume().state)
        print(box.exec("cat /tmp/state.txt").stdout, end="")
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

await using client = new BoxRouter();
await using box = await client.sandboxes.create({ requirements: ['pause_resume'], lifetime_seconds: 900 });
await box.exec('echo saved > /tmp/state.txt');
console.log((await box.pause()).state);
console.log((await box.resume())?.state);
process.stdout.write((await box.exec('cat /tmp/state.txt')).stdout);
```

```bash CLI theme={"theme":"css-variables"}
(
  set -eo pipefail
  id=$(boxrouter create --lifetime-seconds 900 --require pause_resume)
  trap 'boxrouter destroy "$id"' EXIT
  boxrouter exec "$id" 'echo saved > /tmp/state.txt'
  boxrouter api sandboxes pause "$id" | jq -r .state
  boxrouter api sandboxes resume "$id" | jq -r .state
  boxrouter exec "$id" 'cat /tmp/state.txt'
)
```

```bash HTTP theme={"theme":"css-variables"}
(
  set -eo pipefail
  API="${BOXROUTER_BASE_URL:-https://api.boxrouter.ai/api/v1}"
  AUTH="Authorization: Bearer $BOXROUTER_API_KEY"
  id=$(curl -sSf -X POST "$API/sandboxes" -H "$AUTH" -H "Content-Type: application/json" \
    -d '{"requirements": {"pause_resume": {}}, "lifetime_seconds": 900}' | jq -r .id)
  trap 'curl -sSf -o /dev/null -X DELETE "$API/sandboxes/$id" -H "$AUTH"' EXIT
  run() { curl -sSf "$API/sandboxes/$id/commands" -H "$AUTH" -H "Content-Type: application/json" -d "{\"command\": \"$1\"}"; }
  run 'echo saved > /tmp/state.txt' >/dev/null
  curl -sSf -X POST "$API/sandboxes/$id/pause" -H "$AUTH" | jq -r .state
  curl -sSf -X POST "$API/sandboxes/$id/resume" -H "$AUTH" | jq -r .state
  run 'cat /tmp/state.txt' | jq -j .stdout
)
```

Output

```text theme={"theme":"css-variables"}
paused
running
saved
```

Each result also reports what the route kept. `keeps` on a pause result says whether memory was kept as well as files.
`cold_restart` on a resume result is `true` when processes did not survive.

The CLI and HTTP examples request cleanup on exit. If creation's outcome is unknown, a sandbox
may exist without its ID. Follow [Recover from Errors](https://boxrouter.ai/docs/errors.md) before creating another.

While a sandbox is paused, a call that needs it running fails with `409 not_running`. Its `fix`
tells you to resume and then send the call again.

Files always survive a pause. Calling `resume` on a sandbox that is already running does nothing and answers `204`.

### How Long a Paused Sandbox Lasts

Watch `lifetime.kept_until`: BoxRouter destroys a paused sandbox then unless you resume it.
`lifetime.ends_at` is null while paused.

Whether paused time counts against the lifetime depends on the route.

* If it doesn't, `kept_until` is 30 days after the sandbox last ran, or sooner where the
  provider keeps it for less.
* If it does, the lifetime keeps running, so `kept_until` is whichever comes first: the
  lifetime's end or the 30-day mark.

Store files you need beyond that time in a [Volume](https://boxrouter.ai/docs/volumes.md).

Resume continues the remaining lifetime and takes no body.

## Extend the Lifetime

`lifetime_seconds` here is the new total, not an increment. You can extend a running or paused
sandbox. If the outcome is unknown, the provider timer may have moved. Read the sandbox's current
lifetime and follow `fix` before deciding whether to extend again.

Each example creates a new sandbox with an extendable lifetime, extends it, and requests cleanup.
The CLI and HTTP examples need `jq`; the HTTP example also needs `curl`. A lost create response can leave a sandbox without
its ID, so read [Recover from Errors](https://boxrouter.ai/docs/errors.md) first.

```python Python theme={"theme":"css-variables"}
import json

from boxrouter import BoxRouter

with BoxRouter() as client:
    with client.sandboxes.create(
        lifetime_seconds=900,
        resources={"extendable_lifetime": True},
    ) as box:
        extended = box.extend(lifetime_seconds=1800)
        print(json.dumps({"seconds": extended.seconds, "remaining_seconds": extended.remaining_seconds}))
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

await using client = new BoxRouter();
await using box = await client.sandboxes.create({
  lifetime_seconds: 900,
  resources: { extendable_lifetime: true },
});
const extended = await box.extend({ lifetime_seconds: 1800 });
console.log(JSON.stringify({ seconds: extended.seconds, remaining_seconds: extended.remaining_seconds }));
```

```bash CLI theme={"theme":"css-variables"}
(
  set -euo pipefail
  id=''
  cleanup() {
    if [[ -n "$id" ]]; then
      boxrouter destroy "$id" >/dev/null || { echo "Check sandbox $id for cleanup." >&2; exit 1; }
    fi
  }
  trap cleanup EXIT
  id=$(boxrouter create --lifetime-seconds 900 --resources '{"extendable_lifetime":true}')
  [[ -n "$id" ]] || { echo 'No sandbox ID returned.' >&2; exit 1; }
  boxrouter api sandboxes extend "$id" --body '{"lifetime_seconds": 1800}' |
    jq -ec '{seconds, remaining_seconds} | select(.seconds != null and .remaining_seconds != null)'
)
```

```bash HTTP theme={"theme":"css-variables"}
(
  set -euo pipefail
  API=${BOXROUTER_BASE_URL:-https://api.boxrouter.ai/api/v1}
  AUTH="Authorization: Bearer $BOXROUTER_API_KEY"
  id=''
  cleanup() {
    if [[ -n "$id" ]]; then
      curl -sSf -X DELETE "$API/sandboxes/$id" -H "$AUTH" >/dev/null ||
        { echo "Check sandbox $id for cleanup." >&2; exit 1; }
    fi
  }
  trap cleanup EXIT
  id=$(curl -sSf -X POST "$API/sandboxes" -H "$AUTH" -H "Content-Type: application/json" \
    -d '{"lifetime_seconds": 900, "resources": {"extendable_lifetime": true}}' |
    jq -er '.id | strings | select(length > 0)')
  curl -sSf -X POST "$API/sandboxes/$id/lifetime" -H "$AUTH" \
    -H "Content-Type: application/json" -d '{"lifetime_seconds": 1800}' |
    jq -ec '{seconds, remaining_seconds} | select(.seconds != null and .remaining_seconds != null)'
)
```

Output

```text theme={"theme":"css-variables"}
{"seconds":1800,"remaining_seconds":1794}
```

| Error | Cause and Fix |
| - | - |
| `not_named` | The sandbox was created without `resources.extendable_lifetime`. Create the next one with it. |
| `lifetime_limit` | Check `outcome` and the current lifetime before retrying. If the total is too high, `details.max_seconds` gives the limit. |
| `invalid_request` | The total is shorter than the current one. A lifetime cannot shrink. |

On Credits, an extension needs a current price, and BoxRouter must set aside credits for the added time. If either fails,
the extension is refused and the lifetime is unchanged. With BYOK your provider bills the
added time.

## Idle Shutdown

Idle shutdown applies the route's idle action after a period without activity. Request a custom interval at
creation with `custom_idle_time` and `idle_seconds`. Check first with [Match](https://boxrouter.ai/docs/reference/match.md) whether any route
can satisfy it:

```python Python theme={"theme":"css-variables"}
import json
from boxrouter import BoxRouter

with BoxRouter() as client:
    result = client.sandboxes.match(
        requirements={"idle_shutdown": {"custom_idle_time": True, "idle_seconds": 600}},
    )
    print(json.dumps(result.to_dict()["excluded"], indent=2))
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  const result = await client.sandboxes.match({
    requirements: { idle_shutdown: { custom_idle_time: true, idle_seconds: 600 } },
  });
  console.log(JSON.stringify(result.excluded, null, 2));
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter match --requirements '{"idle_shutdown": {"custom_idle_time": true, "idle_seconds": 600}}' | jq .excluded
```

When no route can do it, `excluded` says why:

Output

```json theme={"theme":"css-variables"}
[
{
"provider": "<PROVIDER_ID>",
"failed": [{"reason": "gate_unsupported", "detail": "idle_shutdown: unsupported", "gate": "idle_shutdown"}]
}
]
```

Pass the same `requirements` to `create` once a route is eligible.

* `custom_idle_time` with `idle_seconds` requests a custom idle interval.
* Without `idle_seconds`, the route's own idle time applies.

A route that does not provide idle shutdown is excluded with reason `gate_unsupported`, as shown, so ask
for it only when the job needs it.

## Pause When the Lifetime Ends

By default a sandbox is destroyed at or after the end of its lifetime. Set `at_end` to `pause` and request
`pause_resume` to keep it instead. The paused sandbox follows the `kept_until` rules above.

## Next Steps

> [card] [How a Sandbox Runs and Ends](https://boxrouter.ai/docs/lifecycle.md)
>
> States, what each keeps and bills, and the four clocks.

> [card] [Recover from Errors](https://boxrouter.ai/docs/errors.md)
>
> What to do after a timeout or an unknown outcome.

> [card] [Keep Files across Sandboxes](https://boxrouter.ai/docs/volumes.md)
>
> Keep files when a sandbox ends.


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