---
title: "Boot ONE sandbox from an image (the expensive path; fork_sandbox is the loop) [rate class: sandbox_api_rps]"
method: POST
path: "/preview/sandboxes"
tags: ["sandboxes"]
---

# Boot ONE sandbox from an image (the expensive path; fork_sandbox is the loop) \[rate class: sandbox_api_rps\]

`POST /preview/sandboxes`

## Request

- Base URL: `https://autoresearch.sfcompute.com`
- URL: `https://autoresearch.sfcompute.com/preview/sandboxes`
- Auth: HTTP bearer

Boot ONE fresh sandbox from a curated image, run `setup`, and hand it over running. With NO setup this is cheap after the first call: the first one boots the image, and later calls for the same image and size fork the snapshot that first one left. Forking costs nothing extra. The response reports `reused_snapshot: true` when it uses a snapshot. With a `setup` every call pays a full boot plus the whole setup again, billed as sandbox time - fine for one interactive sandbox, wrong for anything repeated. Either way, if you want N sandboxes, this is still the wrong tool: create_sandbox_env once plus fork_sandbox(count: N) is ONE call for all N instead of N calls, and at any real width the round trips cost more than the sandboxes. Per-sample differences (a seed, an env var, an input file) do not need separate boots either: fork identical copies, then pass the difference as part of each fork's sandbox_exec command. The response includes the `snapshot` this sandbox came from so more copies are one fork_sandbox away, and if your workspace recently booted this same recipe the response carries a `prefix_note` naming the snapshot you could have forked instead. One exception: when the response says `shared_base: true` it came from a base Autoresearch keeps warm - forked from it, or, when `pooled: true` rides beside it, handed over already running (no boot, no fork, billed from the handover). That base is ours and carries no `snapshot` you can fork - another create_sandbox is just as fast, and N copies still want create_sandbox_env plus fork_sandbox.

## Query parameters

- `workspace` string

## Request body

- object
  - `egress` string, nullable: Network: "open" (default) is NAT'd internet - git clone, pip install, DNS, outbound HTTPS - and "none" is no network device at all, for a hermetic run or for code you want to give no egress whatsoever. Neither can reach your nodes, your private networks, or another sandbox. The two are cached separately, so asking for "none" never hands you a sandbox that has a network.
  - `image` string, nullable: Curated base image name (sbx/images.toml). "sbx-base" is the default and carries python3, node 24, git, curl and a compiler; "sbx-min" is bash and coreutils only; "sbx-task" is python3 with numpy. Never a path - an unknown name is refused with the list.
  - `package_cache` boolean, nullable: Serve npm and bun installs from a cache on the machine instead of from registry.npmjs.org. Default false. Faster on a cold install - the packages come off the same host rather than the wire. IT REWRITES YOUR LOCKFILE, which is why it is off by default and why you should not turn it on for a run whose lockfile you keep. The cache serves tarballs from a loopback address, so bun records THAT address for every package: a `bun.lock` produced under it differs from the one you committed, on every dependency. If you diff it, commit it, or hand it to a `--frozen-lockfile` check afterwards, that check fails. Use it for throwaway installs where only the speed matters. It applies to the sandbox you are handed. A `setup` runs during the bake, which always resolves upstream, so a setup that installs pays full price either way.
  - `ram_gib` integer, nullable: Guest RAM in GiB (default 2); bills per RAM-minute while the sandbox lives.
  - `setup` string, nullable: One shell command run after boot, before you get the sandbox. If you will ever run this same setup again, that is create_sandbox_env plus fork_sandbox instead - this parameter re-pays the setup on every call.
  - `size` string, nullable: A named instance type: "sandbox-sm" (1 vCPU, 2 GiB - the default), "sandbox-md" (4 vCPU, 8 GiB), "sandbox-lg" (8 vCPU, 32 GiB) or "sandbox-xl" (16 vCPU, 64 GiB). Sizes with more than one vCPU are what a parallel build, an install or a typecheck needs; a single agent doing one tool call at a time does not. Mutually exclusive with ram_gib, and your ceiling is the sandbox_vcpus limit. Bills by the size's GiB-equivalents, which for the multi-core sizes is more than their RAM: cores are what a big sandbox takes off the box.

## Response `200`

The capability's JSON result.

- Result: The capability's result: the same object the MCP tool returns as structuredContent. Resources carry an `object` type discriminator and an immutable id; money is integer micro-dollars in `*_usd_micros` fields.
  - `object` string: The resource type, when the result is one resource.

## Other responses

- `400`: Malformed request: bad JSON, a parameter that does not parse, or a value outside its range. Code `invalid`.
- `401`: No bearer token, or one this API cannot verify. Code `unauthenticated`.
- `403`: Authenticated, but the token's scopes or the account's role do not reach this operation. The message names the scope word to request. Code `forbidden`.
- `404`: No such route. Code `not_found`; the body names the OpenAPI document.
- `422`: A well-formed request the platform declined: no such node, a cap reached, a state that forbids the move. The message says which, and what to do instead. Code `refused`.
- `429`: Over this door's per-customer budget. Wait `Retry-After` seconds. Code `rate_limited`.
- `500`: Our fault. Retry; if it persists, quote the request-id. Code `internal`.

---

[API](https://skmtc.dev/givemeanode/apis/autoresearch-customer-api.md) · [All operations](https://skmtc.dev/givemeanode/apis/autoresearch-customer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/givemeanode/apis/autoresearch-customer-api/revisions/f88dcb70ac6c?raw)
