Share sandbox with the organization
Let every member of the organization that pays for this sandbox use it.
Your personal logins (GitHub token, Claude and Codex logins, environment secrets,
the sandbox’s own CLI key) are never pushed to it again and are wiped off its disk
at its next start, so a teammate never acts as you. Files you wrote stay. One-way:
the sandbox stays like a noEnv sandbox from then on. A running sandbox stays
view for the organization until you stop and resume it (restartRequired).
Only the person who created the sandbox can share it, and only a sandbox billed to an organization. Sharing an already shared sandbox is a no-op.
curl --request POST \
--url https://boat.dev/api/v1/sandboxes/{sandboxId}/share \
--header 'Authorization: Bearer <token>'import requests
url = "https://boat.dev/api/v1/sandboxes/{sandboxId}/share"
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}/share', 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}/share",
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}/share"
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}/share")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://boat.dev/api/v1/sandboxes/{sandboxId}/share")
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>",
"health": "ok",
"healthReason": "<string>",
"degradedSince": "2023-11-07T05:31:56Z",
"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",
"desktopUrl": "<string>",
"snapshots": 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,
"wipePendingUntilRestart": true,
"subdomain": "<string>",
"lastSnapshotAttemptAt": "2023-11-07T05:31:56Z",
"lastSnapshotStatus": "queued",
"setupStatus": "pending",
"setupError": "<string>",
"environment": "base",
"environmentVersion": 3
},
"restartRequired": true,
"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.
^bx_[23456789abcdefghjkmnpqrstuvwxyz]{8}$curl --request POST \
--url https://boat.dev/api/v1/sandboxes/{sandboxId}/share \
--header 'Authorization: Bearer <token>'import requests
url = "https://boat.dev/api/v1/sandboxes/{sandboxId}/share"
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}/share', 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}/share",
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}/share"
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}/share")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://boat.dev/api/v1/sandboxes/{sandboxId}/share")
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>",
"health": "ok",
"healthReason": "<string>",
"degradedSince": "2023-11-07T05:31:56Z",
"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",
"desktopUrl": "<string>",
"snapshots": 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,
"wipePendingUntilRestart": true,
"subdomain": "<string>",
"lastSnapshotAttemptAt": "2023-11-07T05:31:56Z",
"lastSnapshotStatus": "queued",
"setupStatus": "pending",
"setupError": "<string>",
"environment": "base",
"environmentVersion": 3
},
"restartRequired": true,
"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": {}
}
}