Skip to main content
Sandboxes have real URL endpoints when you start it. Sandboxes have two types of URLs:
  • exposedUrl - This is any other port you wish to expose within the sandbox that is not reserved. You can set up any process you want on the sandbox just like any other host.

Runtime URL vs Exposed Port URL

Every running sandbox includes a runtime base URL for API calls.
const sandbox = await client.sandboxes.create({
  imageName: "node",
});

console.log("Runtime URL:", sandbox.runtime.baseUrl);

// The SDK sends this to sandbox.runtime.baseUrl + /sandbox/exec for you.
const result = await sandbox.exec(`node -e 'console.log("hello from runtime")'`);
console.log(result.stdout.trim());
SDK handles the sandbox runtime layer for you URL for you when you use the runtime functions, see Node SDK sandboxes and Python SDK sandboxes.
To make a service reachable, start it inside the sandbox and expose its port. It looks something like this:
  • runtime.baseUrl looks like https://<runtime-host>/
  • exposed port 3000 looks like https://3000-<runtime-host>/
The SDK can derive that URL locally:
sandbox.getExposedUrl(3000);

Example: Expose a Public HTTP Server

Start a simple HTTP server inside the sandbox, expose port 3000, and call it from outside the sandbox.
const sandbox = await client.sandboxes.create({
  imageName: "node",
});

const server = await sandbox.processes.start(`node -e "const http = require('http');

http
  .createServer((_, res) => {
    res.writeHead(200, { 'content-type': 'text/plain' });
    res.end('hello from sandbox');
  })
  .listen(3000, '0.0.0.0')"`);

await new Promise((resolve) => setTimeout(resolve, 1000));

const exposure = await sandbox.expose({
  port: 3000,
  auth: false,
});

console.log("Service URL:", exposure.url);

const response = await fetch(exposure.url);
console.log(await response.text());

Example: Browser Access with Auth Enabled

When auth is true, Hyperbrowser protects the exposed port.
  • Use exposure.url for programmatic calls with a bearer token.
  • Use exposure.browserUrl for browser access.
  • browserUrl is a bootstrap URL. It sets the auth cookie, then redirects to next.
  • By default, the returned browserUrl redirects to /. If your app lives at /app, change next before opening it.
const sandbox = await client.sandboxes.create({
  imageName: "node",
});

const server = await sandbox.processes.start(`node -e "const http = require('http');

http
  .createServer((req, res) => {
    if (req.url === '/app') {
      res.writeHead(200, { 'content-type': 'text/html' });
      res.end('<h1>Protected app</h1>');
      return;
    }

    if (req.url === '/api/status') {
      res.writeHead(200, { 'content-type': 'application/json' });
      res.end(JSON.stringify({ ok: true }));
      return;
    }

    res.writeHead(404);
    res.end('not found');
  })
  .listen(3000, '0.0.0.0')"`);

await new Promise((resolve) => setTimeout(resolve, 1000));

const exposure = await sandbox.expose({
  port: 3000,
  auth: true,
});

const detail = await sandbox.info();
const apiUrl = new URL("/api/status", exposure.url);
const browserUrl = new URL(exposure.browserUrl);
browserUrl.searchParams.set("next", "/app");

console.log("Protected API URL:", apiUrl.toString());
console.log("Open this in a browser:", browserUrl.toString());

const response = await fetch(apiUrl, {
  headers: {
    Authorization: `Bearer ${detail.token}`,
  },
});

console.log(await response.text());
await sandbox.stop();
browserUrl is not the raw port API URL. It is a bootstrap URL that sets the auth cookie and then redirects to next. Use url for programmatic clients, and use browserUrl when you want to land a browser on a protected page.