OpenClaw Template
OpenClaw is an open-source agent framework that supports CLI-based programmatic invocation and a Gateway Web UI. This article focuses on real-world task scenarios: deploying Gateway, Agent CLI, custom-domain browser access, security mode and operations, and Skill extension. For image addresses, building a Template, and environment/capability notes, see OpenClaw Template.
If this is your first time, please complete the API Key and SDK setup described in Use FC Agent Sandbox with the SDK.
Prerequisites
Completed SDK integration (E2B API Key,
api_url,domain)Built a Template with status
readyfollowing OpenClaw Template, and noted the template name (referred to asTEMPLATE_NAMEbelow) and theOPTSfrom the build examplePrepared a model API Key to inject via
envswhen creating the sandbox — images do not include pre-configured keysChina: Obtain an API Key (starting with
sk-) from the Bailian ConsoleInternational: Obtain
OPENAI_API_KEYfrom the OpenAI Platform
Deploy Gateway
Start the OpenClaw Gateway to interact with the Agent in a browser. The complete workflow below covers: Create Sandbox → Configure → Start Gateway → Get Access URL. For server-side programmatic invocation, see Agent CLI.
China: Bailian
import time
from e2b_code_interpreter import Sandbox
# Gateway auth Token: set any string you like — no need to obtain it from an API.
# Pass it to openclaw gateway --token at startup; the browser accesses it via the URL ?token= parameter.
# The official docs also support the OPENCLAW_APP_TOKEN environment variable, which is equivalent.
TOKEN = "my-gateway-token"
PORT = 18789
BAILIAN_API_KEY = "<your-bailian-api-key>"
BAILIAN_BASE_URL = "https://dashscope.aliyuncs.com/apps/anthropic"
BAILIAN_MODEL = "qwen3.7-max"
# 1. Create Sandbox
sandbox = Sandbox.create(
template=TEMPLATE_NAME,
timeout=3600,
envs={
"ANTHROPIC_AUTH_TOKEN": BAILIAN_API_KEY,
"ANTHROPIC_BASE_URL": BAILIAN_BASE_URL,
"ANTHROPIC_MODEL": BAILIAN_MODEL,
},
**OPTS,
)
# Register the Bailian provider
sandbox.commands.run(
"openclaw onboard --non-interactive --accept-risk --skip-health "
"--auth-choice custom-api-key "
f"--custom-api-key {BAILIAN_API_KEY} "
f"--custom-base-url {BAILIAN_BASE_URL} "
"--custom-compatibility anthropic "
f"--custom-model-id {BAILIAN_MODEL} "
"--custom-provider-id bailian",
timeout=120,
)
# 2. Set default model
sandbox.commands.run(
f"openclaw config set agents.defaults.model.primary bailian/{BAILIAN_MODEL}"
)
# 3. Configure Control UI (required for FC E2B public network access)
origin = f"https://{sandbox.get_host(PORT)}"
sandbox.commands.run(
f"openclaw config set gateway.controlUi.allowedOrigins '[\"{origin}\"]'"
)
# 4. Start Gateway (run in background)
sandbox.commands.run(
f"bash -lc 'openclaw config set gateway.controlUi.allowInsecureAuth true && "
f"openclaw config set gateway.controlUi.dangerouslyDisableDeviceAuth true && "
f"openclaw gateway --allow-unconfigured --bind lan --auth token "
f"--token {TOKEN} --port {PORT}'",
background=True,
)
# 5. Wait for Gateway to be ready
for _ in range(45):
probe = sandbox.commands.run(
f'bash -lc \'ss -ltn | grep -q ":{PORT} " && echo ready || echo waiting\''
)
if probe.stdout.strip() == "ready":
break
time.sleep(1)
url = f"https://{sandbox.get_host(PORT)}/?token={TOKEN}"
print(f"Gateway: {url}")
# X-Access-Token is returned by the SDK after Sandbox.create succeeds, used for FC E2B public port proxy authentication
print(f"Access Token: {sandbox._envd_access_token}")International: OpenAI
import time
from e2b_code_interpreter import Sandbox
# Gateway auth Token: set any string you like — no need to obtain it from an API.
# Pass it to openclaw gateway --token at startup; the browser accesses it via the URL ?token= parameter.
# The official docs also support the OPENCLAW_APP_TOKEN environment variable, which is equivalent.
TOKEN = "my-gateway-token"
PORT = 18789
# 1. Create Sandbox
sandbox = Sandbox.create(
template=TEMPLATE_NAME,
timeout=3600,
envs={"OPENAI_API_KEY": "<your-openai-api-key>"},
**OPTS,
)
# 2. Set default model
sandbox.commands.run(
"openclaw config set agents.defaults.model.primary openai/gpt-4o"
)
# 3. Configure Control UI (required for FC E2B public network access)
origin = f"https://{sandbox.get_host(PORT)}"
sandbox.commands.run(
f"openclaw config set gateway.controlUi.allowedOrigins '[\"{origin}\"]'"
)
# 4. Start Gateway (run in background)
sandbox.commands.run(
f"bash -lc 'openclaw config set gateway.controlUi.allowInsecureAuth true && "
f"openclaw config set gateway.controlUi.dangerouslyDisableDeviceAuth true && "
f"openclaw gateway --allow-unconfigured --bind lan --auth token "
f"--token {TOKEN} --port {PORT}'",
background=True,
)
# 5. Wait for Gateway to be ready
for _ in range(45):
probe = sandbox.commands.run(
f'bash -lc \'ss -ltn | grep -q ":{PORT} " && echo ready || echo waiting\''
)
if probe.stdout.strip() == "ready":
break
time.sleep(1)
url = f"https://{sandbox.get_host(PORT)}/?token={TOKEN}"
print(f"Gateway: {url}")
# X-Access-Token is returned by the SDK after Sandbox.create succeeds, used for FC E2B public port proxy authentication
print(f"Access Token: {sandbox._envd_access_token}")X-Access-Token is not manually requested and is not the Gateway Token. After Sandbox.create(...) succeeds, the SDK exposes _envd_access_token (i.e., the Sandbox Access Token) on the sandbox object. Each sandbox has its own token, valid only while the sandbox is alive; it becomes invalid after the sandbox is destroyed, and a new sandbox must be created to obtain a new value.
Agent CLI
Invoke the Agent programmatically on the server side without starting the Gateway.
If you have already created a Sandbox following the Deploy Gateway section above, run the commands below directly on the same sandbox — no need to create another one.
If you only need the CLI without starting the Gateway:
China: Bailian
from e2b_code_interpreter import Sandbox
BAILIAN_API_KEY = "<your-bailian-api-key>"
BAILIAN_BASE_URL = "https://dashscope.aliyuncs.com/apps/anthropic"
BAILIAN_MODEL = "qwen3.7-max"
sandbox = Sandbox.create(
template=TEMPLATE_NAME,
timeout=3600,
envs={
"ANTHROPIC_AUTH_TOKEN": BAILIAN_API_KEY,
"ANTHROPIC_BASE_URL": BAILIAN_BASE_URL,
"ANTHROPIC_MODEL": BAILIAN_MODEL,
},
**OPTS,
)
# Register the Bailian provider
sandbox.commands.run(
"openclaw onboard --non-interactive --accept-risk --skip-health "
"--auth-choice custom-api-key "
f"--custom-api-key {BAILIAN_API_KEY} "
f"--custom-base-url {BAILIAN_BASE_URL} "
"--custom-compatibility anthropic "
f"--custom-model-id {BAILIAN_MODEL} "
"--custom-provider-id bailian",
timeout=120,
)
result = sandbox.commands.run(
f'openclaw agent --local --agent main --model bailian/{BAILIAN_MODEL} '
'-m "What is 2+2? Reply with just the number."',
timeout=120,
)
print(result.stdout)International: OpenAI
from e2b_code_interpreter import Sandbox
sandbox = Sandbox.create(
template=TEMPLATE_NAME,
timeout=3600,
envs={"OPENAI_API_KEY": "<your-openai-api-key>"},
**OPTS,
)
result = sandbox.commands.run(
'openclaw agent --local --message "What is 2+2? Reply with just the number."',
timeout=120,
)
print(result.stdout)Cleanup
Release resources when the task is complete:
sandbox.kill()Browser Access (FC E2B)
To open the Gateway Control UI in a browser on FC E2B, a custom domain must be bound first. The default platform domain (*.e2b.fc.aliyuncs.com) will trigger a download instead of opening the page; after binding a custom domain, the page loads normally. For the complete steps on adding a domain in the console, DNS resolution, HTTPS certificates, and SDK configuration, see Cloud Sandbox Custom Domains.
Configure Custom Domain
Step 1: Console configuration. Bind a custom domain for the cloud sandbox in the Function Compute console, configure certificates and DNS (api.<your-custom-domain> / *.<your-custom-domain>).
Step 2: Update OPTS in the SDK. Replace the default OPTS from Build Template in OpenClaw Template:
OPTS = {
"api_key": "<your E2B API Key>",
"api_url": "https://api.cn-beijing.e2b.fc.aliyuncs.com",
"domain": "cn-beijing.e2b.fc.aliyuncs.com",
}Replace it with the custom domain matching the console (api_key must belong to the same account that bound the custom domain):
OPTS = {
"api_key": "<your E2B API Key>",
"api_url": "https://api.<your-custom-domain>",
"domain": "<your-custom-domain>",
}Step 3: Pass OPTS throughout the workflow.Template.build(..., **OPTS), Sandbox.create(..., **OPTS), and all other SDK calls in this documentation use the above OPTS — no additional parameters are needed.
After completion, sandbox.get_host(PORT) will return {PORT}-sbx-{sandbox_id}.<your-custom-domain>; the Gateway's allowedOrigins, the browser address bar, and the <host> in the curl command below all use this address.
Open the Control UI
After configuring the custom domain and deploying the Gateway, public network access requires two layers of authentication:
| Authentication | Source | Purpose | How to Pass |
| Sandbox Access Token | sandbox._envd_access_token returned by Sandbox.create | FC E2B port proxy | Request header X-Access-Token |
| Gateway Token | TOKEN defined in your code | OpenClaw Control UI | URL parameter ?token= |
Run the script above and note the
GatewayURL andAccess Tokenin the outputUse a browser extension (e.g., ModHeader) to add the request header:
X-Access-Token: <Access Token>Open the Gateway URL (the URL already contains
?token=)
You can verify with curl first (must include X-Access-Token, otherwise FC E2B returns 403):
curl -sI \
-H "X-Access-Token: <Access Token>" \
"https://<host>/?token=<Gateway Token>"The expected response should include 200; receiving an HTML page indicates the Gateway is running normally.
If you see a "browser origin not allowed" error, make sure allowedOrigins exactly matches the address bar URL (https://{sandbox.get_host(PORT)}, with no trailing /), then restart the Gateway after making changes.
How It Works
| Step | Description |
--bind lan | Gateway listens on 0.0.0.0, allowing E2B to proxy the port |
--auth token | Authentication via URL parameter ?token= |
| Browser opens URL | Gateway serves the UI, browser establishes WebSocket |
code=1008 pairing required | Security mode requires device approval first |
devices approve | Approve the browser device fingerprint |
| Browser reconnects | WebSocket connection succeeds, UI is available |
Security Mode
During testing, device pairing can be disabled (the script above already sets dangerouslyDisableDeviceAuth true). If security mode is enabled, you must approve pending devices after opening the URL:
import json
for _ in range(30):
try:
res = sandbox.commands.run(
f"openclaw devices list --json --url ws://127.0.0.1:{PORT} --token {TOKEN}"
)
data = json.loads(res.stdout)
if data.get("pending"):
rid = data["pending"][0]["requestId"]
sandbox.commands.run(
f"openclaw devices approve {rid} --token {TOKEN} "
f"--url ws://127.0.0.1:{PORT}"
)
print(f"Device approved: {rid}")
break
except Exception:
pass
time.sleep(2)Restart Gateway
After modifying the model or configuration, run the following in the current Sandbox:
origin = f"https://{sandbox.get_host(PORT)}"
sandbox.commands.run(
f"openclaw config set gateway.controlUi.allowedOrigins '[\"{origin}\"]'"
)
sandbox.commands.run(
"""bash -lc 'for p in "[o]penclaw gateway" "[o]penclaw-gateway"; do
for pid in $(pgrep -f "$p" || true); do kill "$pid" 2>/dev/null || true; done
done'"""
)
time.sleep(1)
sandbox.commands.run(
f"openclaw gateway --allow-unconfigured --bind lan --auth token "
f"--token {TOKEN} --port {PORT}",
background=True,
)
for _ in range(45):
probe = sandbox.commands.run(
f'bash -lc \'ss -ltn | grep -q ":{PORT} " && echo ready || echo waiting\''
)
if probe.stdout.strip() == "ready":
break
time.sleep(1)Disable Insecure Settings (Recommended After Testing)
sandbox.commands.run(
"bash -lc 'openclaw config set gateway.controlUi.allowInsecureAuth false && "
"openclaw config set gateway.controlUi.dangerouslyDisableDeviceAuth false'"
)
sandbox.commands.run(
"""bash -lc 'for p in "[o]penclaw gateway" "[o]penclaw-gateway"; do
for pid in $(pgrep -f "$p" || true); do kill "$pid" 2>/dev/null || true; done
done'"""
)
sandbox.commands.run(
f"openclaw gateway --allow-unconfigured --bind lan --auth token "
f"--token {TOKEN} --port {PORT}",
background=True,
)Extend Capabilities with Skills
OpenClaw extends Agent capabilities through Skills (a directory plus SKILL.md). Skills work with both Gateway and Agent CLI. For details, see OpenClaw Skills.
The examples below assume you have already created a sandbox above (via Gateway or Agent CLI). The image does not preload custom Skills; use openclaw skills list to view loaded Skills (including bundled ones). Path conventions are in OpenClaw Template.
The examples below use China Bailian (BAILIAN_MODEL from above). For International, change --model bailian/{BAILIAN_MODEL} to openai/gpt-4o, or omit --model to use the configured default.
Write a Managed Skill:
sandbox.files.write(
"/home/user/.openclaw/skills/summarize-changes/SKILL.md",
"""---
name: summarize-changes
description: Summarizes uncommitted changes and flags risks. Use when reviewing diffs or writing commit messages.
user-invocable: true
---
## Instructions
1. Run `git diff HEAD` and summarize changes in 2–3 bullets.
2. List risks such as missing error handling or hardcoded values.
3. If the diff is empty, say there are no uncommitted changes.
""",
)
# Confirm it appears in the list
print(sandbox.commands.run("openclaw skills list").stdout)
result = sandbox.commands.run(
f'openclaw agent --local --agent main --model bailian/{BAILIAN_MODEL} '
'--message "/summarize-changes"',
timeout=0,
)
print(result.stdout)Write a Workspace Skill:
sandbox.files.write(
"/home/user/.openclaw/workspace/skills/api-conventions/SKILL.md",
"""---
name: api-conventions
description: API design conventions. Use when adding or changing HTTP endpoints or /healthz.
user-invocable: true
---
When writing API endpoints:
- Use RESTful naming
- Return consistent error formats
- Include request validation
""",
)
result = sandbox.commands.run(
f'openclaw agent --local --agent main --model bailian/{BAILIAN_MODEL} '
'--message "/api-conventions Add a /healthz endpoint"',
timeout=0,
)
print(result.stdout)Upload a multi-file Skill directory from your local machine:
If a Skill includes accompanying files such as scripts/ or references/, use files.write_files to write them in one batch instead of calling files.write for each file:
from pathlib import Path
def upload_skill_dir(sandbox, local_dir: str, remote_root: str) -> None:
local = Path(local_dir).resolve()
entries = []
for path in local.rglob("*"):
if path.is_file():
rel = path.relative_to(local).as_posix()
entries.append({
"path": f"{remote_root.rstrip('/')}/{rel}",
"data": path.read_bytes(),
})
sandbox.files.write_files(entries)
# Example local directory layout:
# ./my-skills/echo-marker/SKILL.md
# ./my-skills/echo-marker/scripts/marker.txt
upload_skill_dir(
sandbox,
"./my-skills/echo-marker",
"/home/user/.openclaw/skills/echo-marker", # Managed; for Workspace use workspace/skills/...
)
print(sandbox.commands.run("openclaw skills list").stdout)
result = sandbox.commands.run(
f'openclaw agent --local --agent main --model bailian/{BAILIAN_MODEL} '
'--message "/echo-marker"',
timeout=0,
)
print(result.stdout)About Template.copy: Prefilling files with
.copy("local-dir", "/home/user/.openclaw/skills/...")duringTemplate.buildis not supported yet. Use the runtimefiles.write/files.write_filesapproaches above for custom Skills.
Install from ClawHub (optional):
The sandbox has public outbound access by default, so you can search for and install community Skills directly (browse clawhub.ai):
# Search
result = sandbox.commands.run("openclaw skills search calendar", timeout=120)
print(result.stdout)
# Install
sandbox.commands.run("openclaw skills install ws-calendar", timeout=180)Billing
Sandboxes are billed based on CPU and memory specifications and runtime duration; model API call costs are billed separately by the model provider. See Billing Overview for details.
FAQ
| Issue | Solution |
| Agent not responding | Verify that envs includes the model Key; for China Bailian, confirm onboard has been completed; for International, confirm OPENAI_API_KEY is injected |
| Gateway not opening in browser | Add the X-Access-Token request header; ensure the URL contains ?token= |
| Browser triggers a download | A custom domain must be bound; see Cloud Sandbox Custom Domains and Browser Access (FC E2B) |
sandbox account mismatch | The API Key and custom domain must belong to the same account; see Cloud Sandbox Custom Domains |
| Origin not allowed | Ensure allowedOrigins exactly matches the address bar URL, then restart the Gateway |
| Build failure | Verify that FROM_IMAGE matches the region; do not use the openclaw-v* prefix for name; see OpenClaw Template |
| Other SDK / build issues | See Custom Templates — Troubleshooting |
References
OpenClaw Template (image addresses, build Template, Gateway parameters, environment overview, Skill capability notes)