Sandboxes
Make an organization sandbox private
Make a shared organization sandbox private again, at once. Only you can then use it. Teammates still see it in their list and can stop it. Their SSH keys are removed from the running machine.
Nothing on the sandbox changes: files, installs and its environment stay. Undo with
POST /sandboxes/{sandboxId}/share.
Only the person who created the sandbox can make it private, and only a sandbox billed to an organization. Making a private sandbox private is a no-op.
POST
/
sandboxes
/
{sandboxId}
/
unshare
Make an organization sandbox private
curl --request POST \
--url https://boat.dev/api/v1/sandboxes/{sandboxId}/unshare \
--header 'Authorization: Bearer <token>'import requests
url = "https://boat.dev/api/v1/sandboxes/{sandboxId}/unshare"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://boat.dev/api/v1/sandboxes/{sandboxId}/unshare', 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://boat.dev/api/v1/sandboxes/{sandboxId}/unshare",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://boat.dev/api/v1/sandboxes/{sandboxId}/unshare"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://boat.dev/api/v1/sandboxes/{sandboxId}/unshare")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://boat.dev/api/v1/sandboxes/{sandboxId}/unshare")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"sandbox": {
"id": "bx_23456789",
"name": "Boat 2026-05-31 12:00",
"state": "init",
"desktopAvailable": true,
"snapshotAvailable": true,
"error": "<string>",
"dataExpiredAt": "2023-11-07T05:31:56Z",
"health": "ok",
"healthReason": "<string>",
"degradedSince": "2023-11-07T05:31:56Z",
"hydrated": true,
"hydratedAt": "2023-11-07T05:31:56Z",
"hydration": {
"phase": "<string>",
"entriesDone": 123,
"entriesTotal": 123,
"percent": 123
},
"type": "small",
"vcpu": 4,
"memoryGB": 8,
"billingMultiplier": 1,
"machineProvider": "hetzner",
"url": "<string>",
"ip": "<string>",
"sshEndpoint": "203.0.113.10:22001",
"createdAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z",
"archiveAfter": "2023-11-07T05:31:56Z",
"ttlSeconds": 123,
"desktopUrl": "<string>",
"snapshots": true,
"monitoring": true,
"snapshotCompletedAt": "2023-11-07T05:31:56Z",
"snapshotVerifiedAt": "2023-11-07T05:31:56Z",
"team": {
"id": "<string>",
"name": "<string>"
},
"createdBy": "<string>",
"createdById": "<string>",
"access": "owner",
"holdsCreatorLogins": true,
"visibility": "shared",
"cost": {
"since": "2023-11-07T05:31:56Z",
"seconds": 123,
"dollars": 123
},
"wipePendingUntilRestart": true,
"subdomain": "<string>",
"lastSnapshotAttemptAt": "2023-11-07T05:31:56Z",
"lastSnapshotStatus": "queued",
"setupStatus": "pending",
"setupError": "<string>",
"environment": "base",
"environmentVersion": 3,
"stop": {
"id": "stop_3f9a1c2e7b4d5a60",
"status": "pending",
"requestedAt": "2023-11-07T05:31:56Z",
"lastAttemptAt": "2023-11-07T05:31:56Z",
"error": "<string>",
"endedAt": "2023-11-07T05:31:56Z"
}
},
"message": "<string>"
}{
"ok": false,
"type": "sandbox.error",
"status": 400,
"code": "invalid_json",
"message": "Request body must be valid JSON.",
"error": {
"code": "invalid_json",
"message": "Request body must be valid JSON.",
"status": 400
},
"requestId": "req_01HX..."
}{
"ok": false,
"type": "sandbox.error",
"status": 401,
"code": "unauthorized",
"message": "Unauthorized",
"error": {
"code": "unauthorized",
"message": "Unauthorized",
"status": 401
},
"requestId": "req_01HX..."
}{
"ok": false,
"type": "sandbox.error",
"status": 403,
"code": "forbidden",
"message": "Forbidden",
"error": {
"code": "forbidden",
"message": "Forbidden",
"status": 403
},
"requestId": "req_01HX..."
}{
"ok": false,
"type": "sandbox.error",
"status": 409,
"code": "provider_not_configured",
"message": "Prompting is locked until Codex is configured on the Agents page.",
"requestId": "req_01HX...",
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
}
}Authorizations
Boat bearer token in the form boat_.... Service API keys authenticate sandbox operations.
Path Parameters
Public Sandbox id returned by create/list/get sandbox calls.
Pattern:
^bx_[23456789abcdefghjkmnpqrstuvwxyz]{8}$⌘I
Make an organization sandbox private
curl --request POST \
--url https://boat.dev/api/v1/sandboxes/{sandboxId}/unshare \
--header 'Authorization: Bearer <token>'import requests
url = "https://boat.dev/api/v1/sandboxes/{sandboxId}/unshare"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://boat.dev/api/v1/sandboxes/{sandboxId}/unshare', 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://boat.dev/api/v1/sandboxes/{sandboxId}/unshare",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://boat.dev/api/v1/sandboxes/{sandboxId}/unshare"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://boat.dev/api/v1/sandboxes/{sandboxId}/unshare")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://boat.dev/api/v1/sandboxes/{sandboxId}/unshare")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"sandbox": {
"id": "bx_23456789",
"name": "Boat 2026-05-31 12:00",
"state": "init",
"desktopAvailable": true,
"snapshotAvailable": true,
"error": "<string>",
"dataExpiredAt": "2023-11-07T05:31:56Z",
"health": "ok",
"healthReason": "<string>",
"degradedSince": "2023-11-07T05:31:56Z",
"hydrated": true,
"hydratedAt": "2023-11-07T05:31:56Z",
"hydration": {
"phase": "<string>",
"entriesDone": 123,
"entriesTotal": 123,
"percent": 123
},
"type": "small",
"vcpu": 4,
"memoryGB": 8,
"billingMultiplier": 1,
"machineProvider": "hetzner",
"url": "<string>",
"ip": "<string>",
"sshEndpoint": "203.0.113.10:22001",
"createdAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z",
"archiveAfter": "2023-11-07T05:31:56Z",
"ttlSeconds": 123,
"desktopUrl": "<string>",
"snapshots": true,
"monitoring": true,
"snapshotCompletedAt": "2023-11-07T05:31:56Z",
"snapshotVerifiedAt": "2023-11-07T05:31:56Z",
"team": {
"id": "<string>",
"name": "<string>"
},
"createdBy": "<string>",
"createdById": "<string>",
"access": "owner",
"holdsCreatorLogins": true,
"visibility": "shared",
"cost": {
"since": "2023-11-07T05:31:56Z",
"seconds": 123,
"dollars": 123
},
"wipePendingUntilRestart": true,
"subdomain": "<string>",
"lastSnapshotAttemptAt": "2023-11-07T05:31:56Z",
"lastSnapshotStatus": "queued",
"setupStatus": "pending",
"setupError": "<string>",
"environment": "base",
"environmentVersion": 3,
"stop": {
"id": "stop_3f9a1c2e7b4d5a60",
"status": "pending",
"requestedAt": "2023-11-07T05:31:56Z",
"lastAttemptAt": "2023-11-07T05:31:56Z",
"error": "<string>",
"endedAt": "2023-11-07T05:31:56Z"
}
},
"message": "<string>"
}{
"ok": false,
"type": "sandbox.error",
"status": 400,
"code": "invalid_json",
"message": "Request body must be valid JSON.",
"error": {
"code": "invalid_json",
"message": "Request body must be valid JSON.",
"status": 400
},
"requestId": "req_01HX..."
}{
"ok": false,
"type": "sandbox.error",
"status": 401,
"code": "unauthorized",
"message": "Unauthorized",
"error": {
"code": "unauthorized",
"message": "Unauthorized",
"status": 401
},
"requestId": "req_01HX..."
}{
"ok": false,
"type": "sandbox.error",
"status": 403,
"code": "forbidden",
"message": "Forbidden",
"error": {
"code": "forbidden",
"message": "Forbidden",
"status": 403
},
"requestId": "req_01HX..."
}{
"ok": false,
"type": "sandbox.error",
"status": 409,
"code": "provider_not_configured",
"message": "Prompting is locked until Codex is configured on the Agents page.",
"requestId": "req_01HX...",
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
}
}