OpenClaw Template

Updated at:

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 ready following OpenClaw Template, and noted the template name (referred to as TEMPLATE_NAME below) and the OPTS from the build example

  • Prepared a model API Key to inject via envs when creating the sandbox — images do not include pre-configured keys

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 sandboxno 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:

AuthenticationSourcePurposeHow to Pass
Sandbox Access Tokensandbox._envd_access_token returned by Sandbox.createFC E2B port proxyRequest header X-Access-Token
Gateway TokenTOKEN defined in your codeOpenClaw Control UIURL parameter ?token=
  1. Run the script above and note the Gateway URL and Access Token in the output

  2. Use a browser extension (e.g., ModHeader) to add the request header: X-Access-Token: <Access Token>

  3. 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

StepDescription
--bind lanGateway listens on 0.0.0.0, allowing E2B to proxy the port
--auth tokenAuthentication via URL parameter ?token=
Browser opens URLGateway serves the UI, browser establishes WebSocket
code=1008 pairing requiredSecurity mode requires device approval first
devices approveApprove the browser device fingerprint
Browser reconnectsWebSocket 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/...") during Template.build is not supported yet. Use the runtime files.write / files.write_files approaches 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

IssueSolution
Agent not respondingVerify 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 browserAdd the X-Access-Token request header; ensure the URL contains ?token=
Browser triggers a downloadA custom domain must be bound; see Cloud Sandbox Custom Domains and Browser Access (FC E2B)
sandbox account mismatchThe API Key and custom domain must belong to the same account; see Cloud Sandbox Custom Domains
Origin not allowedEnsure allowedOrigins exactly matches the address bar URL, then restart the Gateway
Build failureVerify that FROM_IMAGE matches the region; do not use the openclaw-v* prefix for name; see OpenClaw Template
Other SDK / build issuesSee Custom Templates — Troubleshooting

References