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

# Configure a Launch Manifest

> Look up every manifest field, the order launch prepares them in, and the time limits that bound preparation.

A manifest defines the image, files, setup, and command for one launch. You can supply it in three ways:

* Send it inline as `manifest`.
* Load a file with `--manifest`.
* Launch a catalog name that resolves to a manifest.

Unknown fields are rejected with `400 invalid_request`. The full request and response schemas are in the
[launch endpoint](https://boxrouter.ai/docs/reference/launch-create.md).

## Start with an Inline Manifest

An inline manifest needs only `name` and `command`. Set `BOXROUTER_API_KEY` to your API key; the SDKs and CLI
also read an optional `BOXROUTER_BASE_URL`. For HTTP, set `BOXROUTER_BASE_URL` to
`https://api.boxrouter.ai/api/v1`. The CLI and HTTP commands use `jq` and `uuidgen`, and HTTP uses curl 7.76 or later.
This launch runs a setup command, then writes a file:

```python Python theme={"theme":"css-variables"}
import json
import sys
from uuid import uuid4

from boxrouter import BoxRouter
from boxrouter.models import AgentManifest, LaunchOperation, LaunchRequestOneOf1

manifest = AgentManifest.from_dict(
    {
        "name": "shell",
        "command": "bash",
        "lifetime_seconds": 300,
        "setup": ["echo ready > /tmp/setup.txt"],
        "files": {"~/agent-config.json": {"mode": "development"}},
    }
)

with BoxRouter() as client:
    key = str(uuid4())
    print(f"Idempotency key: {key}", file=sys.stderr, flush=True)
    operation = client.api.launches.create(
        body=LaunchRequestOneOf1(manifest=manifest),
        idempotency_key=key,
    )
    if not isinstance(operation, LaunchOperation):
        raise RuntimeError("Unexpected launch reply; use the printed key to recover.")
    print(json.dumps({"id": operation.id, "status": operation.status}))
```

```typescript TypeScript theme={"theme":"css-variables"}
import { randomUUID } from 'node:crypto';

import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  const key = randomUUID();
  console.error(`Idempotency key: ${key}`);
  const operation = await client.api.launches.create({
    body: {
      manifest: {
        name: 'shell',
        command: 'bash',
        lifetime_seconds: 300,
        setup: ['echo ready > /tmp/setup.txt'],
        files: { '~/agent-config.json': { mode: 'development' } },
      },
    },
    headers: { 'Idempotency-Key': key },
  });
  console.log(JSON.stringify({ id: operation.id, status: operation.status }));
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
(
  set -euo pipefail
  key=$(uuidgen)
  echo "Idempotency key: $key" >&2
  printf '%s' '{"manifest":{"name":"shell","command":"bash","lifetime_seconds":300,
       "setup":["echo ready > /tmp/setup.txt"],
       "files":{"~/agent-config.json":{"mode":"development"}}}}' |
    boxrouter api launches create --body - --idempotency-key "$key" |
    jq -ec '{id, status}'
)
```

```bash HTTP theme={"theme":"css-variables"}
(
  set -euo pipefail
  key=$(uuidgen)
  echo "Idempotency key: $key" >&2
  response=$(curl -sS --fail-with-body -X POST "$BOXROUTER_BASE_URL/launches" \
    -H "Authorization: Bearer $BOXROUTER_API_KEY" \
    -H "Idempotency-Key: $key" \
    -H "Content-Type: application/json" \
    -d '{"manifest":{"name":"shell","command":"bash","lifetime_seconds":300,
         "setup":["echo ready > /tmp/setup.txt"],
         "files":{"~/agent-config.json":{"mode":"development"}}}}') ||
    { printf '%s\n' "$response" >&2; exit 1; }
  printf '%s' "$response" |
    jq -ec 'select((.id | type) == "string" and (.status | type) == "string") | {id, status}' ||
    { echo 'Unexpected launch reply; use the printed key to recover.' >&2; exit 1; }
)
```

Output

```json theme={"theme":"css-variables"}
{"id":"<LAUNCH_ID>","status":"queued"}
```

BoxRouter checks the manifest, the secrets, the routes, and the funding before it accepts a launch. A launch it cannot start answers `400`, `402`, or `409` with `outcome` set to `not_started`, and nothing is created.

Otherwise the reply is `202` with a launch operation. API error bodies go to stderr in the CLI and HTTP examples. If the reply is lost, replace the generated key with the
printed key and resend the same request. [Recover the launch](https://boxrouter.ai/docs/launch/your-own-agent.md#call-the-launch-api)
explains what to check before retrying.

Replace `LAUNCH_ID` below with the returned `id`. Run the call until `status` is `succeeded`
or `failed`:

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

from boxrouter import BoxRouter

with BoxRouter() as client:
    operation = client.api.launches.get_operation("LAUNCH_ID").to_dict()
    result = operation.get("result") or {}
    print(
        json.dumps(
            {
                "status": operation["status"],
                "phases": [phase["name"] for phase in result.get("phases", [])],
                "sandbox_id": result.get("sandbox_id") or operation.get("sandbox_id"),
                "effective_lifetime_seconds": result.get("effective_lifetime_seconds"),
                "failure_status": operation.get("failure_status"),
                "error": operation.get("error"),
            },
            indent=2,
        )
    )
```

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

const client = new BoxRouter();
try {
  const operation = await client.api.launches.getOperation({ path: { operation_id: 'LAUNCH_ID' } });
  console.log(
    JSON.stringify({
      status: operation.status,
      phases: (operation.result?.phases ?? []).map(phase => phase.name),
      sandbox_id: operation.result?.sandbox_id ?? null,
      effective_lifetime_seconds: operation.result?.effective_lifetime_seconds ?? null,
      failure_status: operation.failure_status ?? null,
      error: operation.error ?? null,
    }, null, 2),
  );
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
(
  set -euo pipefail
  boxrouter api launches get-operation LAUNCH_ID |
    jq -e '{
      status,
      phases: [.phases[].name],
      sandbox_id: (.result.sandbox_id // .sandbox_id),
      effective_lifetime_seconds: .result.effective_lifetime_seconds,
      failure_status,
      error
    }'
)
```

```bash HTTP theme={"theme":"css-variables"}
(
  set -euo pipefail
  response=$(curl -sS --fail-with-body "$BOXROUTER_BASE_URL/launches/LAUNCH_ID" \
    -H "Authorization: Bearer $BOXROUTER_API_KEY") ||
    { printf '%s\n' "$response" >&2; exit 1; }
  printf '%s' "$response" |
    jq -e '{
      status,
      phases: [.phases[].name],
      sandbox_id: (.result.sandbox_id // .sandbox_id),
      effective_lifetime_seconds: .result.effective_lifetime_seconds,
      failure_status,
      error
    }'
)
```

A successful launch returns the sandbox ID and the phases it completed:

Output

```json theme={"theme":"css-variables"}
{
"status": "succeeded",
"phases": ["create", "setup", "files"],
"sandbox_id": "<SANDBOX_ID>",
"effective_lifetime_seconds": 300,
"failure_status": null,
"error": null
}
```

If the operation fails, inspect `error` and `failure_status`. Before retrying, follow
[preparation failure recovery](#recover-a-preparation-failure).

For a successful launch, replace `SANDBOX_ID` with its ID to request deletion:

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

with BoxRouter() as client:
    destroyed = client.sandboxes.get("SANDBOX_ID").destroy()
    print(destroyed.state, destroyed.operation_id)
```

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

const client = new BoxRouter();
try {
  const destroyed = await (await client.sandboxes.get('SANDBOX_ID')).destroy();
  console.log(destroyed.state, destroyed.operation_id);
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter destroy SANDBOX_ID | jq -r .state
```

```bash HTTP theme={"theme":"css-variables"}
(
  set -euo pipefail
  response=$(curl -sS --fail-with-body -X DELETE "$BOXROUTER_BASE_URL/sandboxes/SANDBOX_ID" \
    -H "Authorization: Bearer $BOXROUTER_API_KEY") ||
    { printf '%s\n' "$response" >&2; exit 1; }
  printf '%s\n' "$response" | jq -r .state
)
```

`destroyed` confirms deletion. `destroying`, with an `operation_id`, means it is pending; [check the operation or sandbox state](https://boxrouter.ai/docs/sandboxes.md#destroy-it).
For a terminal and `--rm`, see the [quickstart](https://boxrouter.ai/docs/launch/quickstart.md).

## Fields

Only `name` and `command` are required. Most launches add some of the fields that prepare the sandbox:
`image`, `code`, `setup`, `files`, `env`, `secrets`, `background` and `ports`. The last four rows,
`required_env`, `max_copies`, `model_access` and `sign_in`, describe catalog agents. A field the table does
not list is refused with `400 invalid_request` before the launch is accepted.

A sandbox can run past the end of its lifetime, `lifetime_seconds`; see [lifetime behavior](https://boxrouter.ai/docs/lifecycle.md#the-four-clocks).

| Field | Purpose |
| - | - |
| `name` | Required. A lowercase identifier for the agent. |
| `command` | Required. Command to run in an attached terminal or as an unattended process after preparation succeeds. |
| `description` | Optional text describing the agent. |
| `image` | Omit to use the standard image. See [images](https://boxrouter.ai/docs/images.md#bring-your-own-image). |
| `setup` | Shell commands run in order before files and background commands. |
| `env` | Non-secret environment values applied to setup and the launch. |
| `secrets` | Names whose values are resolved by the server and added only to the create environment. See [workspace secrets](https://boxrouter.ai/docs/launch/secrets.md). |
| `files` | Relative sandbox paths mapped to UTF-8 text or JSON objects, written before background commands. |
| `volume` | BoxRouter-managed volume reference and access mode attached to the created sandbox. |
| `background` | Shell commands started detached during preparation before the launch command starts. |
| `ports` | Ports to expose as previews. Naming ports adds the previews requirement to routing. |
| `interactive` | When true, a client can attach a terminal; when false, launch starts the command unattended. Default true. |
| `preinstalled` | Declares that the launch's image already carries this agent's harness. Default false. |
| `resources` | Resource constraints, execution limits, and environment facts that must hold for this sandbox. |
| `lifetime_seconds` | Requested sandbox lifetime in seconds for this launch; the sandbox may run past its lifetime deadline. Null asks for a sandbox that never ends. |
| `placement` | Provider preferences for which routes to use and how to rank them, and the route pin that a create takes as `route`. |
| `code` | Clone a GitHub repository at the requested ref before setup. |
| `source` | For a launch, fetch this manifest file at the selected repository ref, then apply the other supplied manifest fields as overrides. |
| `required_env` | Names of environment variables that must have nonblank values when launching the agent. See [launch an agent](https://boxrouter.ai/docs/launch/agents.md). |
| `max_copies` | Maximum copies of this resolved manifest in one launch run. Omission adds no manifest-specific ceiling. See [launch an agent](https://boxrouter.ai/docs/launch/agents.md). |
| `model_access` | Named secrets that configure this catalog agent for model access. A launch may select at most one. See [launch an agent](https://boxrouter.ai/docs/launch/agents.md). |
| `sign_in` | What to do inside the agent when no recognized model secret is selected. See [launch an agent](https://boxrouter.ai/docs/launch/agents.md). |

Keep credentials out of `env`, file contents, and the manifest. Declare names in `secrets` instead. For
private code, [link the repository](https://boxrouter.ai/docs/launch/private-repositories.md) first.

## Preparation Order

Launch pins any source manifest and code revision, then prepares one sandbox in the order below.

![Launch resolves the source and image, creates the sandbox, then runs code, setup, files, background and previews inside one 240-second budget. The command starts last.](https://mintcdn.com/nv-4965711d/RMVlH8Z1jdQT60zr/images/diagrams/launch-preparation-light.svg?fit=max\&auto=format\&n=RMVlH8Z1jdQT60zr\&q=85\&s=4bf8e5d3daa1befe9e11c7ed9a34aac2)

![Launch resolves the source and image, creates the sandbox, then runs code, setup, files, background and previews inside one 240-second budget. The command starts last.](https://mintcdn.com/nv-4965711d/RMVlH8Z1jdQT60zr/images/diagrams/launch-preparation-mobile-light.svg?fit=max\&auto=format\&n=RMVlH8Z1jdQT60zr\&q=85\&s=ae8e0372273fa96575ecd08c90695a99)

The five steps after create share one 240-second budget. A slow setup leaves less for files and previews.

Two phases appear only when needed:

* `source` appears only for a source manifest.
* `image` appears only when the manifest asks for a build or a prepared copy.

For an interactive manifest, the CLI runs `command` when it attaches the terminal; API callers open their own terminal. An unattended manifest starts `command` last.

## Understand Launch Limits

| Limit | Value |
| - | - |
| Preparation after create | 240 seconds, shared by `code`, `setup`, `files`, `background` and `previews`. |
| Attached terminal | At most 3600 seconds. |
| Lifetime | `lifetime_seconds`, or `null` for a sandbox that runs until destroyed. `--lifetime SECONDS` sets one for a launch. |

Without a lifetime of your own, launch leaves the field out: the create request's default applies.
A credit-funded sandbox on a route that can extend a lifetime [runs while funded](https://boxrouter.ai/docs/run-while-funded.md);
otherwise the sandbox runs for 300 seconds. `effective_lifetime_seconds` reports what you got.

A lifetime you choose is never lowered. If it exceeds every such account's cap, launch fails with
`lifetime_cap_exceeded`. Its `fix` gives the maximum or says to connect an account with a higher tier.

A sandbox with no lifetime (`lifetime_seconds: null`) runs until you destroy it. Only a route whose
provider never ends a sandbox can serve it, and only on your own provider account; see
[lifecycle](https://boxrouter.ai/docs/lifecycle.md).

## Recover a Preparation Failure

A failed preparation step can return `launch_failed` with a `phase`. When a command fails, the error
also gives its `command` and `exit_code`. A route that cannot start an unattended command can instead return
`capability_unavailable`. Launch requests sandbox deletion. An `exit 7` in `setup` produced this error:

Output

```json theme={"theme":"css-variables"}
{
"code": "launch_failed",
"details": {
"cleanup": "confirmed",
"command": "exit 7",
"exit_code": 7,
"phase": "setup",
"step_index": 1
},
"fix": "Correct the failed preparation step; the sandbox was deleted. Retry with a new Idempotency-Key.",
"message": "Launch preparation failed during setup step 2."
}
```

Before retrying, read `cleanup`. If it is not `confirmed`, or the operation outcome is unknown, follow
[Recover from Errors](https://boxrouter.ai/docs/errors.md) before sending another request that changes state. Even when cleanup is confirmed,
setup commands may have changed external systems. Check their effects before retrying.

The exit status of a started unattended command is not known at launch. Read it with
`GET /launches/{id}/command`.

## Next Steps

> [card] [Launch Your Own Agent](https://boxrouter.ai/docs/launch/your-own-agent.md)
>
> Launch a manifest from the CLI or the API.

> [card] [Choose a Sandbox Image](https://boxrouter.ai/docs/images.md)
>
> Choose and prepare the image.

> [card] [Run Copies of an Agent](https://boxrouter.ai/docs/launch/copies.md)
>
> Start N unattended copies and read what each wrote.


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