Fork Sandbox
curl --request POST \
--url https://api.declaw.ai/sandboxes/{sandbox_id}/fork \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"name": "<string>",
"timeout": 123,
"envs": {},
"network": {},
"security": {},
"metadata": {},
"lifecycle": {}
}
'import requests
url = "https://api.declaw.ai/sandboxes/{sandbox_id}/fork"
payload = {
"name": "<string>",
"timeout": 123,
"envs": {},
"network": {},
"security": {},
"metadata": {},
"lifecycle": {}
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: '<string>',
timeout: 123,
envs: {},
network: {},
security: {},
metadata: {},
lifecycle: {}
})
};
fetch('https://api.declaw.ai/sandboxes/{sandbox_id}/fork', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.declaw.ai/sandboxes/{sandbox_id}/fork",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'timeout' => 123,
'envs' => [
],
'network' => [
],
'security' => [
],
'metadata' => [
],
'lifecycle' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.declaw.ai/sandboxes/{sandbox_id}/fork"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"timeout\": 123,\n \"envs\": {},\n \"network\": {},\n \"security\": {},\n \"metadata\": {},\n \"lifecycle\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.declaw.ai/sandboxes/{sandbox_id}/fork")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"timeout\": 123,\n \"envs\": {},\n \"network\": {},\n \"security\": {},\n \"metadata\": {},\n \"lifecycle\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.declaw.ai/sandboxes/{sandbox_id}/fork")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"timeout\": 123,\n \"envs\": {},\n \"network\": {},\n \"security\": {},\n \"metadata\": {},\n \"lifecycle\": {}\n}"
response = http.request(request)
puts response.read_bodySandbox API
Fork Sandbox
Create a new sandbox from an existing sandbox’s snapshot, inheriting its filesystem.
POST
/
sandboxes
/
{sandbox_id}
/
fork
Fork Sandbox
curl --request POST \
--url https://api.declaw.ai/sandboxes/{sandbox_id}/fork \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"name": "<string>",
"timeout": 123,
"envs": {},
"network": {},
"security": {},
"metadata": {},
"lifecycle": {}
}
'import requests
url = "https://api.declaw.ai/sandboxes/{sandbox_id}/fork"
payload = {
"name": "<string>",
"timeout": 123,
"envs": {},
"network": {},
"security": {},
"metadata": {},
"lifecycle": {}
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: '<string>',
timeout: 123,
envs: {},
network: {},
security: {},
metadata: {},
lifecycle: {}
})
};
fetch('https://api.declaw.ai/sandboxes/{sandbox_id}/fork', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.declaw.ai/sandboxes/{sandbox_id}/fork",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'timeout' => 123,
'envs' => [
],
'network' => [
],
'security' => [
],
'metadata' => [
],
'lifecycle' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.declaw.ai/sandboxes/{sandbox_id}/fork"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"timeout\": 123,\n \"envs\": {},\n \"network\": {},\n \"security\": {},\n \"metadata\": {},\n \"lifecycle\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.declaw.ai/sandboxes/{sandbox_id}/fork")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"timeout\": 123,\n \"envs\": {},\n \"network\": {},\n \"security\": {},\n \"metadata\": {},\n \"lifecycle\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.declaw.ai/sandboxes/{sandbox_id}/fork")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"timeout\": 123,\n \"envs\": {},\n \"network\": {},\n \"security\": {},\n \"metadata\": {},\n \"lifecycle\": {}\n}"
response = http.request(request)
puts response.read_bodyForks a sandbox: the new sandbox boots from a snapshot of the origin, so it
starts with the origin’s filesystem already in place. The two are independent
from that point on — writes in the fork are not visible to the origin, and the
origin keeps running.
Use this to branch an expensive setup (dependencies installed, dataset
downloaded, model warmed) into several parallel workers without repeating the
setup in each one.
The origin’s template and resources are inherited (they must match the
snapshot, or Firecracker rejects the restore), so
All fields are optional in the sense that the request succeeds without them —
but an omitted field means “unset on the fork”, not “copy the origin”.
A fork requires an existing snapshot. Take one with
Create Snapshot first, or the request
fails with
404 no snapshot to fork from.Without
snapshot_id, the newest snapshot is not necessarily chosen.
Resolution is by source first, then recency: newest pause → newest
periodic → newest manual.So if the sandbox has ever been paused, a fork with no snapshot_id uses that
pause snapshot — even when the manual snapshot you just took is newer. Pass
snapshot_id explicitly whenever it matters which point in time you fork from.template is ignored in the
request body. name falls back to the origin’s when omitted.
Everything else — timeout, metadata, envs, network, lifecycle,
security — is taken from this request, not copied from the origin. Fields
you can set at create time but which are not read here at all (vault_refs,
volumes, secure, resources) are simply ignored.
Path Parameters
string
required
The sandbox to fork from. Format:
sbx-<hex>.Query Parameters
string
Fork from a specific snapshot instead of the most recent usable one. The
snapshot must belong to this sandbox and to your account.
Body Parameters
Policy is not inherited. Only the template, resources and
name come from
the origin. security, network, envs, metadata, lifecycle and
timeout are taken from this request — omit them and the fork gets none,
not the origin’s.So forking a hardened sandbox without repeating its security and network
policy produces an unhardened fork. Re-send the policy you want on every
fork.string
Name for the forked sandbox. Unlike Create — which derives the name from the
template — Fork honours this field.
integer
Auto-kill timeout in seconds, applied independently of the origin. It runs from
creation and is not extended by activity — running commands does not reset
it. Pass
0 to disable auto-kill.object
Environment variables for the fork. Replaces the origin’s, rather than merging.
object
Network policy for the fork. See Network Policy.
object
Security policy for the fork. See security policies.
object
Arbitrary string key/value pairs.
object
Lifecycle configuration for the fork, applied independently of the origin.
Response
Returns201 with the full sandbox object for the fork — the same shape as
Create Sandbox. sandbox_id is new; the origin
is unchanged.
Example
# 1. snapshot the origin
curl -X POST https://api.declaw.ai/sandboxes/sbx-a1b2c3d4/snapshot \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "after-setup"}'
# 2. fork from THAT snapshot explicitly -- see the warning above
curl -X POST "https://api.declaw.ai/sandboxes/sbx-a1b2c3d4/fork?snapshot_id=snap-abc123" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "worker-1", "timeout": 1800}'
import requests
H = {"X-API-Key": "YOUR_API_KEY"}
BASE = "https://api.declaw.ai/sandboxes/sbx-a1b2c3d4"
snap = requests.post(f"{BASE}/snapshot", headers=H, json={"name": "after-setup"}).json()
# Pass snapshot_id explicitly: a default fork resolves pause > periodic > manual,
# so a previous pause snapshot would win over the one just taken.
forks = [
requests.post(
f"{BASE}/fork",
headers=H,
params={"snapshot_id": snap["snapshot_id"]},
json={"name": f"worker-{i}"},
).json()
for i in range(3)
]
print([f["sandbox_id"] for f in forks])
Response
{
"sandbox_id": "sbx-9f8e7d6c",
"template_id": "tpl-base",
"state": "live",
"node_id": "node-declaw-worker-2"
}
Error Responses
| Status | Cause |
|---|---|
400 | Invalid body, or invalid envs / security / custom_policy rego |
401 | Missing or invalid API key |
402 | Insufficient balance to run the fork |
404 | Sandbox not found, snapshot not found, or no snapshot to fork from |
422 | Snapshot predates overlay capture and cannot be forked, or the origin has no resource config |
429 | Concurrent sandbox limit reached for your tier |
502 | Orchestrator unreachable or returned non-200 |
503 | No orchestrator available, reservation failed, or wallet service unavailable |