curl --request POST \
--url https://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"command": "npm install",
"timeout_ms": 300000
}
'import requests
url = "https://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command"
payload = {
"command": "npm install",
"timeout_ms": 300000
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({command: 'npm install', timeout_ms: 300000})
};
fetch('https://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command', 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://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command",
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([
'command' => 'npm install',
'timeout_ms' => 300000
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command"
payload := strings.NewReader("{\n \"command\": \"npm install\",\n \"timeout_ms\": 300000\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"command\": \"npm install\",\n \"timeout_ms\": 300000\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"command\": \"npm install\",\n \"timeout_ms\": 300000\n}"
response = http.request(request)
puts response.read_body{
"stdout": "added 12 packages in 3s\n",
"stderr": "",
"exit_code": 0,
"truncated": false,
"duration_ms": 3120,
"unsaved_large_files": []
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}Run a sandbox command
Runs a shell command inside the app’s sandbox with bash and returns its output.
Every sandbox-bridge endpoint runs against the app’s live sandbox, the same filesystem the AI edits, so a change here is visible in the app editor immediately.
A command that exits non-zero still answers 200. Read exit_code to tell a failed command from a failed request, and expect stderr to carry output on success too, since many tools log there.
Each call starts in the app root unless you set cwd, and cd does not carry over between calls, so chain directory changes inside one command instead. The default timeout is 120000 ms and the maximum is 600000 ms; a command that outruns its timeout answers 504. Output is capped at 1 MB per stream, and truncated tells you when that happened.
If a background save holds the sandbox for more than about 30 seconds, the call answers 409 with SANDBOX_BUSY; retry in a minute.
This endpoint is limited to 30 requests per minute per app, its own budget rather than one shared with the other sandbox-bridge endpoints.
CREDITS_EXHAUSTED. Otherwise the bridge needs a Builder plan or higher on the app’s workspace, and answers 402 below that (PREMIUM_REQUIRED). Workspace API keys are not authorized and are rejected with a 403, and it is unavailable for agent apps. A personal API key works as-is. An OAuth access token needs the sandbox:write scope.extra_data.code alongside the human-readable message. Branch on the code rather than on the message text or the status.curl --request POST \
--url https://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"command": "npm install",
"timeout_ms": 300000
}
'import requests
url = "https://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command"
payload = {
"command": "npm install",
"timeout_ms": 300000
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({command: 'npm install', timeout_ms: 300000})
};
fetch('https://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command', 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://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command",
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([
'command' => 'npm install',
'timeout_ms' => 300000
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command"
payload := strings.NewReader("{\n \"command\": \"npm install\",\n \"timeout_ms\": 300000\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"command\": \"npm install\",\n \"timeout_ms\": 300000\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/apps/{app_id}/sandbox-bridge/run_command")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"command\": \"npm install\",\n \"timeout_ms\": 300000\n}"
response = http.request(request)
puts response.read_body{
"stdout": "added 12 packages in 3s\n",
"stderr": "",
"exit_code": 0,
"truncated": false,
"duration_ms": 3120,
"unsaved_large_files": []
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}Authorizations
Personal access token, sent as Authorization: Bearer <token>. Each operation lists the permission it needs: read_only or full_access. A full-access token can call every operation. A read-only token can call only the operations that list read_only; anything else returns 403 read_only_credential_route_not_allowed.
Path Parameters
ID of the app whose sandbox to operate on.
Body
Shell command to execute via bash inside the sandbox.
1 - 100000Optional Base44 branch ID. Omit to operate on main.
Working dir relative to the app root. cd does not persist across calls — use this or chain commands.
Timeout in milliseconds (default 120000, max 600000).
1 <= x <= 600000Response
The command ran. Read exit_code for its result.
What a command left behind.
Everything the command wrote to standard output.
"added 12 packages in 3s\n"
Everything the command wrote to standard error. Populated on success too, since many tools log there.
""
The command's exit status. 0 means success. A non-zero status is still a 200 from this endpoint: the command ran and failed, which is not a request error.
0
true when the output hit the 1 MB cap and was cut short. stdout and stderr are each capped, and either one hitting it sets this.
false
How long the command took, in milliseconds.
3120
Files in the app over the 32 MiB per-file limit. They can't be saved to the app's code, so they stay in the sandbox only: the published app doesn't have them, and they're lost if the sandbox is rebuilt. Checked after commands that can change files; empty after read-only ones.
Show child attributes
Show child attributes
[]
Was this page helpful?