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

# HTTP API

> Authenticate requests and manage sandboxes, snapshots, templates, and usage over HTTP.

## Authentication

Send requests to:

```text theme={null}
https://api.sandbox.lithosai.cloud
```

Use an API key from **Console → API Keys** in the `Authorization` header. Requests
operate within the key's organization. The following example expects the key in
`LITHOSBOX_TOKEN` and lists that organization's sandboxes:

```sh theme={null}
curl -fsS https://api.sandbox.lithosai.cloud/vms \
  -H "Authorization: Bearer $LITHOSBOX_TOKEN"
```

Send JSON request bodies with `Content-Type: application/json`. Fields ending in
`_unix_ns` use Unix nanoseconds; fields ending in `_unix` use Unix seconds.
Time-range query parameters use RFC3339 timestamps.

## Sandboxes

### Create a sandbox

`POST /vms` returns `201` and a sandbox object. Send `{}` to use the default
environment, or choose at most one source:

| Field         | Purpose                                 |
| ------------- | --------------------------------------- |
| `image`       | OCI image reference.                    |
| `template`    | Template ID, name, or `name:version`.   |
| `snapshot_id` | Snapshot to restore into a new sandbox. |

Optional fields are `cpus`, `memory_mb`, `runtime`, `disable_internet`,
`writable_size_bytes`, and `vm_id`. See [Limits](/reference/limits) for valid values.
Template and snapshot restores retain their source configuration.

```json theme={null}
{
  "image": "python:3.12-slim",
  "cpus": 2,
  "memory_mb": 2048
}
```

Provide a UUID in `vm_id` and reuse it when retrying the same create request. If
you lose the response, check `GET /vms/{id}` before issuing a new create. The
Python SDK supplies and reuses an ID automatically within each create call.

### Find or delete sandboxes

| Route              | Response                          |
| ------------------ | --------------------------------- |
| `GET /vms`         | `{"vms": [...]}`                  |
| `GET /vms/{id}`    | Sandbox object                    |
| `DELETE /vms/{id}` | `204`; repeating deletion is safe |

The sandbox object includes these fields:

| Field               | Meaning                                       |
| ------------------- | --------------------------------------------- |
| `vm_id`             | Sandbox ID.                                   |
| `tenant`            | Organization ID.                              |
| `state`             | Current sandbox state.                        |
| `image`, `runtime`  | Starting image and runtime.                   |
| `cpus`, `memory_mb` | CPU and memory configuration.                 |
| `disable_internet`  | Whether outbound internet access is disabled. |
| `created_unix_ns`   | Creation time in Unix nanoseconds.            |
| `parent_vm_id`      | Original sandbox ID when created by a fork.   |

Common states are `creating`, `running`, `slept`, `archived`, `interrupted`,
`deleting`, and `failed`. Operations may also report `migrating`.
Use the state and error response to decide which action is available; see
[Lifecycle actions](#lifecycle-actions).

### Run a command

`POST /vms/{id}/exec` accepts an argument list:

```json theme={null}
{
  "args": ["python3", "-c", "print(42)"]
}
```

Use `stdin_b64` for base64-encoded input, with at most 4 MiB of decoded data.
For shell syntax, pass `args` such as `["sh", "-c", "pwd && ls"]`.

The response includes `exit_code`, `stdout`, `stderr`, `stdout_truncated`, and
`stderr_truncated`. Check the exit code; a successful HTTP response does not mean
the command succeeded. Foreground completion does not imply every child process
has exited. See [Run commands](/guides/run-commands) for background-job patterns.

### Keep a shell session

Use the same `session_id` for commands that need shared working-directory and
environment state:

```json theme={null}
{
  "session_id": "my-shell",
  "command": "mkdir -p /work && cd /work"
}
```

Send another command with that ID to continue in the same shell. Close it by
sending `{"session_id": "my-shell", "close_session": true}` to the same endpoint.
Sessions have the same synchronous command deadline as ordinary execution.

### Lifecycle actions

| Route                   | Response and behavior                                                                                   |
| ----------------------- | ------------------------------------------------------------------------------------------------------- |
| `POST /vms/{id}/sleep`  | `{"slept": true}` or `{"slept": false}`. A busy sandbox stays awake rather than having its work frozen. |
| `POST /vms/{id}/pause`  | `204`. Alias for `/sleep`; returns `409` for a busy sandbox.                                            |
| `POST /vms/{id}/resume` | `204`. Wake a sleeping sandbox now. Any command also wakes it.                                          |
| `POST /vms/{id}/stop`   | `{"stopped": true}`. Archive the sandbox for later.                                                     |
| `POST /vms/{id}/start`  | `{"started": true, ...}`. Unarchive and continue.                                                       |
| `POST /vms/{id}/reboot` | Updated sandbox object. Keep files and restart the environment; processes and sessions end.             |

The HTTP routes for archive and unarchive are `/stop` and `/start`. An archived
sandbox needs an explicit `/start`; commands do not unarchive it. A sleeping
sandbox wakes on a command or file operation, whether it was slept by `/sleep`
or by the idle policy; `/resume` wakes it without sending a command.

Reboot returns `409` for an archived sandbox; unarchive first. Restart your
application's servers after rebooting. See [Recovery](/launch#recovery) for
interrupted or unavailable sandboxes.

### Fork a sandbox

`POST /vms/{id}/branch` creates an independent copy and returns `201` with a
sandbox object. You may supply a UUID in `child_vm_id` for retrying a single fork.

To request several copies from the same point, send `{"count": 4}`. Counts from
2 to 4 return `{"vms": [...]}`. Copies count toward the organization's sandbox limit.

Forks include files and running processes. Fork between commands, and reconnect
external services independently in each copy. See [Save, restore, fork](/guides/save-restore).

## Public endpoints

| Route                   | Request or response                                                                                          |
| ----------------------- | ------------------------------------------------------------------------------------------------------------ |
| `POST /vms/{id}/expose` | Send `{"guest_port": 8000}`. Returns `201` with `ingress_id`, `guest_port`, `public_port`, and `public_url`. |
| `GET /vms/{id}/ingress` | `{"ingresses": [...]}`                                                                                       |
| `DELETE /ingress/{id}`  | `204`. Remove the endpoint.                                                                                  |

Use `public_url` to access an HTTP server listening on the specified sandbox port.
Leave `public_port` unset or zero to use the assigned port. Anyone with the URL
can reach the application, so add authentication where needed.

An endpoint on an archived sandbox becomes usable after unarchive. The URL is
preserved, but it may briefly return `404` during reconnection. Deleting the
sandbox makes the endpoint unavailable. See [Expose a web service](/guides/serve).

## Snapshots and exports

### Save and manage snapshots

| Route                     | Request or response                                                                                      |
| ------------------------- | -------------------------------------------------------------------------------------------------------- |
| `POST /vms/{id}/snapshot` | Optional `{"leave_paused": false, "snapshot_id": "YOUR_UUID"}`. Returns `201` with snapshot information. |
| `GET /vms/{id}/snapshots` | `{"snapshots": [...]}` for the original sandbox.                                                         |
| `GET /snapshots`          | `{"snapshots": [...]}` for the organization.                                                             |
| `GET /snapshots/{id}`     | Snapshot object.                                                                                         |
| `DELETE /snapshots/{id}`  | `204`. An in-progress export may prevent deletion.                                                       |

A snapshot object includes `snapshot_id`, `vm_id`, `parent_snapshot_id`, `state`,
`durable`, `image`, `logical_bytes`, and `created_unix_ns`. Poll the snapshot until
`durable` is `true` before relying on it for recovery or exporting it.

The original sandbox keeps running unless you request `leave_paused=true`. That
option lets it rest until the next command or file access, which wakes it automatically.

Restore by sending `{"snapshot_id": "YOUR_SNAPSHOT_ID"}` to `POST /vms`.
Restoring creates a new sandbox; deleting the original does not delete its snapshots.

### Export a snapshot

| Route                         | Request or response                                                                                 |
| ----------------------------- | --------------------------------------------------------------------------------------------------- |
| `POST /snapshots/{id}/export` | Optional `{"export_id": "YOUR_UUID"}`. Returns `202` with `export_id`. Requires a durable snapshot. |
| `GET /exports/{id}`           | Export status, downloadable `files` when ready, and `error` if failed.                              |
| `DELETE /exports/{id}`        | `204`. Remove a completed export's files. Returns `409` while it is pending.                        |

An export moves from `pending` to `ready` or `failed`. When ready, `files` contains
links for `memory.img.zst` and `disk.img.zst` compressed with Zstandard, plus
`state.json` and `manifest.json` metadata. Each file entry includes its name, size,
checksum, and download URL. `expires_unix` gives the export's expiry time in Unix
seconds; download the files before the links expire.

Exports are for downloading and inspecting saved data. The API does not import an
export to restore a sandbox; use the original snapshot ID for restoration.

Deleting the source snapshot does not revoke a completed export. Delete the
export separately if you want to remove its files before expiry.

## Templates

`POST /templates` creates a reusable environment. `name` and `image` are required;
`version`, `cpus`, `memory_mb`, `runtime`, and `disable_internet` are optional:

```json theme={null}
{
  "name": "python-workspace",
  "image": "python:3.12-slim",
  "cpus": 2,
  "memory_mb": 2048
}
```

| Route                    | Response                                                         |
| ------------------------ | ---------------------------------------------------------------- |
| `POST /templates`        | `201` with a template object.                                    |
| `GET /templates`         | `{"templates": [...]}`                                           |
| `GET /templates/{id}`    | Template object.                                                 |
| `DELETE /templates/{id}` | `204`; only templates owned by your organization can be deleted. |

Template fields include `template_id`, `name`, `version`, `source_image`,
`status`, `status_detail`, `runtime`, `base_snapshot_id`, `created_unix_ns`, and
`last_used_unix_ns`. Wait for `status="ready"` before creating from the template.
If it becomes `failed`, read `status_detail` for the reason.

The list can include read-only shared templates marked `shared=true`. You can also
look up a shared template ID returned in an image-preparation error. A later create
using the same image can retry preparation if a shared template failed.

## Registries

`POST /registries` stores credentials for a registry and returns `204`. For a
username and token or password, use:

```json theme={null}
{
  "host": "ghcr.io",
  "kind": "static",
  "username": "YOUR_USERNAME",
  "secret": "YOUR_REGISTRY_TOKEN"
}
```

For an ECR role, use `host`, `kind="ecr-assume-role"`, `role_arn`, and `external_id`.
These credentials apply to your organization's image pulls.

| Route                       | Response                                                                                                         |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `GET /registries`           | `{"registries": [...]}` with `host`, `kind`, `created_unix_ns`, and `updated_unix_ns`. Secrets are not returned. |
| `DELETE /registries/{host}` | `204`. Remove the stored credential.                                                                             |

See [Private registries](/guides/images#use-a-private-registry) for an SDK example
that reads credentials from environment variables.

## Activity and metrics

| Route                                            | Response                                                                   |
| ------------------------------------------------ | -------------------------------------------------------------------------- |
| `GET /vms/{id}/events?limit=200`                 | `{"events": [...]}` with `kind`, `detail`, `node`, and `at`, newest first. |
| `GET /vms/{id}/logs?tail_bytes=65536`            | `{"content": "...", "truncated": false}` with system-log output.           |
| `GET /vms/{id}/metrics?since_unix=&max_samples=` | `{"samples": [...], "cpus": n}` with recent resource measurements.         |
| `GET /vms/{id}/metrics/history?hours=24`         | `{"points": [...]}` with historical measurements.                          |

Recent samples contain `at_unix`, `interval_s`, `cpu_ms`, `mem_mb`, `rx_bytes`,
`tx_bytes`, and `state`. They cover approximately two hours at 30-second intervals.

History uses minute intervals and accepts `hours` from 1 to 360, or `start` and
`end` as RFC3339 timestamps, up to 15 days back. A point with `measured=false`
represents a missing measurement.

Events and historical metrics can be queried after sandbox deletion within their
retention periods. Live logs are unavailable for archived sandboxes; reading logs
from a sleeping sandbox does not wake it.

## Usage

`GET /usage?start=<RFC3339>&end=<RFC3339>` returns organization usage, defaulting
to the last 24 hours. A query window can cover up to 32 days.

The response contains `start`, `end`, a `states` array with `state`, `vm_seconds`,
`cpu_seconds`, and `memory_mb_seconds`, and a `snapshot_byte_seconds` total.
An empty `states` array means no usage was recorded for the window.

Usage is reported in minute intervals, so short runs and state changes may be
grouped within an interval. Snapshot usage reflects retained size over time;
deleted snapshots stop accumulating usage.

`GET /usage/series?hours=` or `start=&end=` returns a time series for activity charts.

## Audit

`GET /audit?limit=200&before=` returns `{"records": [...], "next_before": ...}`.
Records include `id`, `key_id`, `action`, `resource`, `outcome`, `request_id`, and
`at`, newest first. Use `next_before` as the next request's `before` value to
continue through older records.

## Status codes

| Code or error                                        | Meaning                                                | Next action                                                                 |
| ---------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------- |
| `400`                                                | Invalid request or field value.                        | Check the request body and [limits](/reference/limits).                     |
| `401`                                                | Missing or invalid credentials.                        | Check the API key.                                                          |
| `403`                                                | Insufficient permission.                               | Check the key's organization and access.                                    |
| `403` with `{"error":"quota","kind":"cap"}`          | Active sandbox limit reached.                          | Archive or delete an unused sandbox.                                        |
| `404`                                                | Resource not found in the key's organization.          | Check the ID and organization.                                              |
| `409`                                                | An action conflicts with the resource's current state. | Read the error and resolve that state before retrying.                      |
| `429` with `Retry-After`                             | Request budget reached.                                | Wait for the indicated interval, then retry.                                |
| `503` with `template_id`                             | Image preparation is in progress.                      | Poll the template until ready, then retry the create.                       |
| `503` with `{"error":"capacity","kind":"placement"}` | Capacity is temporarily unavailable.                   | Retry with backoff; respect `Retry-After` when provided.                    |
| `503` with `sandbox agent unreachable`               | The sandbox is unresponsive.                           | Try rebooting to retain files, or delete and recreate it.                   |
| Other `5xx`                                          | The service could not complete the request.            | Check the resource state before retrying a change whose outcome is unknown. |

Common `409` messages include `sandbox is archived (sandbox is stopped)`
(unarchive first), `sandbox is busy` (a sleep was refused because running work
keeps the sandbox awake), a template that is not ready,
or a snapshot with an export in progress. A checkpoint attempt on an
unresponsive sandbox can also return `409` with `sandbox agent unreachable`.

Match errors on the SDK's typed exceptions — `SandboxArchivedError` for the
archived refusal — rather than on message text. Message wording can change; the
state names reported by the API do not. If you must inspect the text, match
`archived`.

Follow `Retry-After` for rate-limited requests. `x-ratelimit-*` headers are advisory
and describe the budget observed by that response. DELETE requests are exempt
from rate limits. See [Request rates](/reference/limits#request-rates).
