---
title: "Bake an env once (boot, warm, snapshot); fork it forever [rate class: sandbox_api_rps]"
method: POST
path: "/preview/sandboxes/envs"
tags: ["sandboxes"]
---

# Bake an env once (boot, warm, snapshot); fork it forever \[rate class: sandbox_api_rps\]

`POST /preview/sandboxes/envs`

## Request

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

Bake a sandbox environment ONCE, then fork it forever - the first call of every sandbox workflow and the only place setup cost should ever be paid. Boots a fresh microVM from a curated image, runs your `warm` command (installs, clones, downloads, cache warming, server startup - everything each run would otherwise repeat), then snapshots the fully initialized machine - memory, running processes, page cache, disk - and returns a snapshot id. Baking bills as ordinary sandbox time, but it happens ONCE: fork_sandbox then resumes that exact post-setup state. Copies are free (copy-on-write). So the shape that wins, for N samples: create_sandbox_env once -> fork_sandbox(count: N) -> one array-shaped sandbox_exec -> delete_sandbox each. N samples never need N bakes or N create_sandbox calls - they need 1 bake and N forks. Sandboxes reach the internet by default, and nothing private: pass egress: "none" for an env whose forks have no network device at all. If the environment you want ALREADY EXISTS as a container image - an RL task image, a framework's official image, a SWE-bench instance - pass it as from_image (digest-pinned) instead of `image` and skip re-expressing its setup as a warm script; it is converted to a guest root filesystem on first use and cached after that.

## Query parameters

- `workspace` string

## Request body

- object
  - `egress` string, nullable: Network for every fork of this env: "open" (default) gives them NAT'd internet - git clone, pip install, DNS, outbound HTTPS - while "none" gives them no network device at all, so nothing inside can reach anything, including this API. Use "none" when the rollout must be hermetic or the code is untrusted enough that you want no egress at all; everything the sandbox needs then has to be baked in by `warm`, which is where it belongs anyway. Either way a sandbox can never reach your nodes, your private networks, or another sandbox. This is fixed at bake time and every fork inherits it: fork_sandbox cannot change it, and a "none" env cannot be given a network later.
  - `expires_after` string, nullable: Self-cleaning expiry for the baked snapshot: a duration from now ("24h", "7d"; minimum 1h), or "never". Snapshots NEVER expire unless you set this - the default is forever, and the timer is yours (set_sandbox_snapshot_expiry changes it later). Typical use: a fork base you only need for today's batch.
  - `from_image` string, nullable: Your OWN container image to convert and boot, instead of a curated one: any OCI image, named by digest - "ghcr.io/acme/task@sha256:<64 hex>". Use this when the environment you want already exists as a Docker image - an RL task image, a framework's official image, a SWE-bench instance - so you can import it rather than re-express its setup as a `warm` script. It is converted to a guest root filesystem the first time you name it and cached after that, so the first call pays the pull (minutes for a large image) and later calls for the same digest do not. Mutually exclusive with `image`. Must be pinned by digest, not a tag: a tag can be moved to point at different bytes, and the conversion is cached under the image's identity, so a tag would eventually boot you something that is no longer what that name means. Read the digest with `docker buildx imagetools inspect <ref>` or `crane digest <ref>`. A private registry works if your org has a connection covering the ref (autor connection create --kind registry); a public one needs nothing. What the image declares for Env and WorkingDir is honoured; its Entrypoint and User are not - commands run as root through your own shell, the way they do in every other sandbox.
  - `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.
  - `ram_gib` integer, nullable: Guest RAM in GiB (default 2). Every fork of the baked snapshot inherits this size, and it bills per RAM-minute - so bake the smallest env that fits. Your ceiling is the sandbox_ram_gib limit.
  - `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.
  - `warm` string, nullable: The setup to bake in, as one shell command (chain with &&). Put EVERYTHING every rollout would repeat here - package installs, repo clone, model/data download, cache warming, starting a server - because forks resume AFTER this ran: work done here is done once, ever, instead of once per sample.

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