Sandboxes

Networking & egress

Every sandbox runs with one of three egress policies, chosen with the network argument at create time. The default is off - no network at all.

  • off (default) - no network device. The sandbox cannot reach anything. Maximum isolation.
  • restricted - egress only through a forward proxy that allows a fixed set of hosts over HTTPS.HTTP_PROXY and HTTPS_PROXY are already set inside the sandbox, so proxy-aware tools (pip, npm, curl, standard HTTP libraries) work as-is. Direct DNS and raw sockets do not resolve.
  • open - unrestricted egress. Use only when you trust the code or genuinely need arbitrary network access.

Every mode supports pause and resume - a networked sandbox's IP is held across the pause and restored on resume, so long-lived sessions can park with restricted or open networking too.

The default restricted allowlist

On restricted, a sandbox can reach a curated set of hosts that cover the things agent code usually needs - package registries, source hosting and the major model APIs:

  • Python packages - pypi.org, pythonhosted.org
  • npm - registry.npmjs.org
  • Debian apt - deb.debian.org, security.debian.org
  • Source + releases - github.com, githubusercontent.com
  • Model APIs - OpenAI, Anthropic, Google, Mistral, Cohere, Groq, and Hugging Face

Each entry also matches its subdomains, and only HTTPS (port 443) is allowed. The live list is returned as default_egress_domains from GET /v1/sandboxes/limits (and Sandbox.limits() in the SDKs).

A custom egress allowlist

When your code needs a host the default set does not cover - an internal API, a private package mirror, a vendor endpoint - declare your own allowlist instead of opening the sandbox up to the whole internet. It is a tier strictly between restricted and open: still HTTPS-only, still proxy-mediated, still no raw sockets or DNS - just scoped to the domains you name.

A custom list replaces the default - it does not add to it
When you pass allow_domains, those domains become the entire allowlist for that sandbox. Include any registries you still need (e.g. pypi.org if you pip install). Start from default_egress_domains and edit from there. Custom allowlists are available on every plan.

Pass it per sandbox at create time:

custom-allowlist
from orkestr import Sandbox

# Restricted egress, but reach your own hosts instead of the default set.
sbx = Sandbox.create(
    template="python-3.12",
    network="restricted",
    allow_domains=["pypi.org", "pythonhosted.org", "api.internal.acme.com"],
)

# See the default set to start from:
print(Sandbox.limits().default_egress_domains)

Entries are bare hostnames - no scheme, port, path or wildcard.api.internal.acme.com is matched along with its subdomains. The list only takes effect on restricted; it is ignored for off and open.

Baking a default into a template

You can also bake an allowlist into a custom template. Every restricted sandbox booted from that template inherits the list, so a team can standardize the reachable hosts once. A create call's own allow_domains still wins over the baked default when both are present.

template with a baked allowlist
curl -X POST https://api.orkestr.eu/v1/templates \
  -H "Authorization: Bearer $ORKESTR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme-agent",
    "base_template": "python-3.12",
    "recipe": ["pip install httpx"],
    "network": "restricted",
    "allow_domains": ["pypi.org", "pythonhosted.org", "api.internal.acme.com"]
  }'

# Every restricted sandbox booted from this template inherits the list,
# unless the create call passes its own allow_domains (which wins).
Resolution order
For a restricted sandbox the effective allowlist is, in order: the create call's allow_domains if given, else the template's baked list if it has one, else the platform default set.

Public preview URLs

Egress is about what a sandbox can reach out to. Preview URLs are the other direction: exposing an HTTP port a process inside the sandbox is serving at a public URL. Open it in a browser or embed it in your app and you get a live preview of a dev server running inside the sandbox.

Call sbx.get_host(port) (or GET /v1/sandboxes/{id}/host?port=N) and you get back a hostname of the form <port>-<id>.sbx.orkestr.run, where <id> is the sandbox's public id with the sbx_ prefix dropped and lowercased. A sandbox sbx_01HXYZ... serving on port 3000 is reachable at https://3000-01hxyz....sbx.orkestr.run.

preview-url
from orkestr import Sandbox

# A networked sandbox is required - "off" has no interface to expose.
sbx = Sandbox.create(template="node-22", network="open")

# Start a server inside the sandbox (here, in the background):
sbx.exec("nohup python3 -m http.server 3000 >/tmp/srv.log 2>&1 &")

host = sbx.get_host(3000)     # "3000-01hxyz....sbx.orkestr.run"
url = f"https://{host}"       # open it in a browser or embed it in your app
The URL is public by default
With no auth, a preview URL's only capability is the unguessable sandbox id baked into the hostname - treat it like a secret link: anyone who has it reaches the port while the sandbox is running. The host is stable for the sandbox's lifetime but serves traffic only while the sandbox is running - a paused or terminated sandbox returns nothing. To require a token instead, see Lock a preview down below.

Two requirements: the sandbox must be created with network="restricted" or network="open" (an off sandbox has no network interface and no port to expose), and exposing a public port needs a card on file - the free tier cannot. WebSockets ride through, so a dev server's hot-reload (HMR) works through the preview URL. For building these links in a UI, the sandbox GET and list payloads also carry preview_host_base - the port-less <id>.sbx.orkestr.run suffix, or null when the sandbox cannot expose a port.

Lock a preview down

Pass preview_auth=True at create (previewAuth: true in JS) and the sandbox's preview URLs stop being public: every request must carry the minted preview_auth_token or the edge returns 401. Use it when the served content should not be reachable by anyone who simply learns the URL.

preview-auth
from orkestr import Sandbox

# Opt in at create: preview URLs now require an access token.
sbx = Sandbox.create(template="node-22", network="open", preview_auth=True)
sbx.exec("nohup python3 -m http.server 3000 >/tmp/srv.log 2>&1 &")

sbx.preview_auth_token          # the minted secret, e.g. "wUc7dj8_..."

# 1) Browser / iframe: preview_url() appends the token as a query param.
#    First hit -> 302 that sets an ork_preview_token cookie, so the page
#    AND its relative sub-resources (css/js/images) stay authenticated.
embed_url = sbx.preview_url(3000)
#   https://3000-<id>.sbx.orkestr.run?ork_preview_token=wUc7dj8_...

# 2) Programmatic (curl / fetch / a backend proxy): send the header instead.
#    curl -H "X-Orkestr-Preview-Token: <token>" https://3000-<id>.sbx.orkestr.run

# Without a valid token the edge returns 401. A public sandbox (the default,
# no preview_auth) has preview_auth_token = None and needs nothing.

There are three ways to present the token, and they cover both audiences:

  • Header - X-Orkestr-Preview-Token: <token>. The clean path for programmatic callers (curl, fetch, a backend proxying the preview).
  • Query param - ?ork_preview_token=<token>, what preview_url(port) builds. On the first hit the edge answers with a redirect that sets an ork_preview_token cookie, so the page and every relative sub-resource it loads (CSS, JS, images) stay authenticated - this is what makes a full app embed cleanly in an <iframe>, not just a single file.
  • Cookie - ork_preview_token=<token>, how the browser carries it after that first redirect (you rarely set this yourself).

The token is minted once at create and returned only to the owner (on the create response and subsequent Sandbox.get / list). Enforcement happens at the preview edge, so it applies no matter which of the three forms the caller uses. Leaving preview_auth off keeps the default public behaviour - existing embeds need no change.

Reading with an agent? This page is also plain markdown at /docs/sandboxes/networking.md, and the full docs index lives at /docs/llms.txt.