Skip to main content
Port proxy lets external HTTP clients reach any port inside a running sandbox. Each sandbox gets a stable URL pattern based on its ID and port number, so you can start a web server, API, or MCP server inside the VM and access it from outside without SSH tunnels or port-forwarding configuration.

Quick start

Start an HTTP server inside a sandbox, then access it from anywhere using the proxy URL.
from declaw import Sandbox

sbx = Sandbox.create(template="python")

# Start a web server inside the sandbox
sbx.commands.run("nohup python3 -c \"\n"
    "from http.server import HTTPServer, BaseHTTPRequestHandler\n"
    "class H(BaseHTTPRequestHandler):\n"
    "    def do_GET(self):\n"
    "        self.send_response(200)\n"
    "        self.send_header('Content-Type', 'text/plain')\n"
    "        self.end_headers()\n"
    "        self.wfile.write(b'Hello from sandbox!')\n"
    "HTTPServer(('', 8080), H).serve_forever()\n"
    "\" &>/dev/null &")

import time; time.sleep(1)

# Get the public URL
url = sbx.get_host(8080)
print(url)
# https://api.declaw.ai/sandboxes/sbx-.../ports/8080
The returned URL is a fully qualified HTTPS endpoint. Any HTTP client (browser, curl, SDK) can send requests to it, authenticated with your API key in the X-API-Key header.

How it works

  1. Your client sends an HTTP request to https://api.declaw.ai/sandboxes/{sandbox_id}/ports/{port}/{path}.
  2. Declaw authenticates the request using the X-API-Key header.
  3. If allow_public_traffic is enabled (the default), the request is proxied to the target port inside the sandbox.
  4. The response from the in-sandbox server is returned to the client.
For WebSocket requests, the same auth and validation runs first. Once authenticated, the connection is upgraded and Declaw maintains a bidirectional tunnel between your client and the in-sandbox server for the lifetime of the connection. Subpaths are preserved. A request to .../ports/8080/api/v1/users reaches the sandbox as GET /api/v1/users.

MCP servers

Declaw provides a convenience method for the common pattern of running an MCP server on port 50005 inside a sandbox.
url = sbx.get_mcp_url()
print(url)
# https://api.declaw.ai/sandboxes/sbx-.../ports/50005/mcp
This is equivalent to calling get_host(50005) + "/mcp". Use the mcp-server template to get a sandbox pre-configured with FastMCP and the standard MCP dependencies.

WebSocket

Port proxy supports WebSocket connections. Use the same URL from get_host(), replacing https:// with wss://.
import asyncio
import websockets
from declaw import Sandbox

sbx = Sandbox.create(template="python")

# Start a WebSocket echo server inside the sandbox
sbx.commands.run("pip install websockets -q", timeout=30)
sbx.files.write("/tmp/ws.py", """
import asyncio, websockets

async def echo(ws):
    async for msg in ws:
        await ws.send("echo: " + msg)

async def main():
    async with websockets.serve(echo, "0.0.0.0", 8765):
        await asyncio.Future()

asyncio.run(main())
""")
sbx.commands.run("nohup python3 /tmp/ws.py &>/dev/null &")

import time; time.sleep(2)

# Connect via WebSocket
url = sbx.get_host(8765).replace("https://", "wss://")

async def main():
    async with websockets.connect(url, additional_headers={"X-API-Key": "YOUR_API_KEY"}) as ws:
        await ws.send("hello")
        print(await ws.recv())  # "echo: hello"

asyncio.run(main())
WebSocket connections require the same X-API-Key header as HTTP requests, passed during the upgrade handshake via additional_headers (Python) or connection options (TypeScript/Go).

Security

Authentication

All port proxy requests require a valid X-API-Key header, the same key used for every other sandbox operation. Unauthenticated requests receive HTTP 401.

Disabling port proxy

Port proxy access is controlled by the allow_public_traffic field in the sandbox’s network configuration. It defaults to true. Set it to false to block all inbound port proxy requests.
from declaw import Sandbox

sbx = Sandbox.create(
    network={"allow_public_traffic": False}
)
# Requests to sbx.get_host(8080) will receive HTTP 403

Blocked ports

Port 49983 is reserved for internal use and cannot be proxied. Requests targeting this port return HTTP 403. Set-Cookie headers are stripped from all proxied responses to prevent cookie injection attacks against the API domain.

Limits

LimitValue
Request body size100 MiB (HTTP only)
Supported HTTP methodsGET, POST, PUT, PATCH, DELETE, HEAD
WebSocketSupported (upgrade via GET)
Blocked ports49983 (reserved)

CORS

CORS preflight (OPTIONS) requests are handled automatically by the proxy and do not require an X-API-Key header. This allows browser-based applications to call sandbox services directly without a backend relay.
All other HTTP methods (GET, POST, etc.) still require the X-API-Key header, even when called from a browser. Use a backend proxy or serverless function if you cannot expose your API key to the client.