Port Allowlist
Sandboxes default to closed for public proxy access. A port inside a sandbox is only reachable from the public internet after an explicit exposePort call. This prevents unauthenticated access to databases, debug endpoints, or accidental listeners.
Port Access URLs
Section titled “Port Access URLs”Once a port is exposed, it is reachable via two equivalent URLs:
1. Dedicated port URL - returned directly by exposePort:
https://<sandbox-id>-<port>.<domain>2. Proxy path - constructed from the sandbox's main public URL:
https://<sandbox-id>.<domain>/proxy/<port>/Both URLs become active after exposePort and return 403 before it. The dedicated URL is the cleaner base URL for web apps and APIs. The proxy path is always available in both domain mode and IP-only mode.
Expose a Port
Section titled “Expose a Port”const sandbox = await client.create({ image: 'ubuntu:22.04' })
// Before exposing - 403await fetch(`${sandbox.publicURL}/proxy/21212/`)
// Expose the port - returns the discriminated ExposeResultconst exposure = await sandbox.exposePort(21212)// exposure.url → "https://<sandbox-id>-21212.sandbox.example.com"
// Both URLs now reach the serviceawait fetch(exposure.url) // 200 - dedicated URLawait fetch(`${sandbox.publicURL}/proxy/21212/`) // 200 - proxy pathsandbox = client.create({'image': 'ubuntu:22.04'})
# Before exposing - 403# GET sandbox['publicURL'] + '/proxy/21212/'
exposure = sandbox.expose_port(21212)# exposure.url → 'https://<sandbox-id>-21212.sandbox.example.com'
# Both URLs now reach the service# GET exposure.url # dedicated URL# GET sandbox['publicURL'] + '/proxy/21212/' # proxy pathsandbox, err := client.Create(ctx, sdktypes.CreateSandboxOptions{ Image: "ubuntu:22.04",})if err != nil { log.Fatal(err) }
// Before exposing - 403// GET sandbox.PublicURL + "/proxy/21212/"
exposure, err := sandbox.ExposePort(ctx, 21212)if err != nil { log.Fatal(err) }// exposure.PublicURL → "https://<sandbox-id>-21212.sandbox.example.com"
// Both URLs now reach the servicefmt.Println(exposure.PublicURL) // dedicated URLfmt.Println(sandbox.PublicURL + "/proxy/21212/") // proxy pathlet sandbox = client.create(CreateOptions { image: "ubuntu:22.04".to_string(), ..Default::default()})?;
// Before exposing - 403// GET sandbox.data.public_url + "/proxy/21212/"
let exposure = sandbox.expose_port(21212, ExposeOptions::default())?;if let ExposeResult::Http { url } = &exposure { // url → "https://<sandbox-id>-21212.sandbox.example.com" println!("{}", url); // dedicated URL}println!("{}/proxy/21212/", sandbox.data.public_url); // proxy pathSandbox sandbox = client.create( new CreateOptions().setImage("ubuntu:22.04"));
// Before exposing - 403// GET sandbox.publicUrl + "/proxy/21212/"
ExposeResult exposure = sandbox.exposePort(21212);// exposure.url → "https://<sandbox-id>-21212.sandbox.example.com"
// Both URLs now reach the serviceSystem.out.println(exposure.url); // dedicated URLSystem.out.println(sandbox.publicUrl + "/proxy/21212/"); // proxy pathUnexpose a Port
Section titled “Unexpose a Port”Removes the port from the allowlist and tears down its public route. Both URLs return 403 again immediately.
await sandbox.unexposePort(21212)await fetch(`${sandbox.publicURL}/proxy/21212/`) // 403sandbox.unexpose_port(21212)# GET sandbox['publicURL'] + '/proxy/21212/' → 403if err := sandbox.UnexposePort(ctx, 21212); err != nil { log.Fatal(err) }// GET sandbox.PublicURL + "/proxy/21212/" → 403sandbox.unexpose_port(21212)?;// GET sandbox.data.public_url + "/proxy/21212/" → 403sandbox.unexposePort(21212);// GET sandbox.publicUrl + "/proxy/21212/" → 403How it works
Section titled “How it works”The allowlist is maintained server-side. Every exposePort and unexposePort call updates the allowlist and creates or removes the corresponding Caddy route. When a sandbox stops and restarts, the allowlist is restored automatically so previously exposed ports become reachable again without any extra calls.
Diagnosing 403 responses
Section titled “Diagnosing 403 responses”- Confirm the port appears in
exposed_portsonGET /v1/sandboxes/<id>. - Check that
exposePortcompleted without error. - Verify the sandbox is in
runningstate - stopped sandboxes do not serve traffic.