diff --git a/docs.json b/docs.json
index 1a813472..0ebb722e 100644
--- a/docs.json
+++ b/docs.json
@@ -56,6 +56,15 @@
"docs/agents/amp",
"docs/agents/claude-code",
"docs/agents/codex",
+ {
+ "group": "OpenClaw",
+ "icon": "/images/icons/openclaw.svg",
+ "pages": [
+ "docs/agents/openclaw-gateway",
+ "docs/agents/openclaw-telegram",
+ "docs/agents/openclaw-slack"
+ ]
+ },
"docs/agents/opencode"
]
},
diff --git a/docs/agents/openclaw-environment.mdx b/docs/agents/openclaw-environment.mdx
new file mode 100644
index 00000000..2ecc9aca
--- /dev/null
+++ b/docs/agents/openclaw-environment.mdx
@@ -0,0 +1,331 @@
+---
+title: "OpenClaw Environment"
+description: "Change models, rotate tokens, update channels, and reset OpenClaw state in a running E2B sandbox."
+icon: "sliders"
+---
+
+Use this guide for post-deploy changes in a running OpenClaw sandbox using SDK calls.
+
+## What you can change without rebuilding
+
+| Setting | Rebuild required? | Notes |
+|---|---|---|
+| Default model (`agents.defaults.model.primary`) | No | Applies to new runs |
+| Gateway token (`--token`) | No | Restart gateway process with new token |
+| Telegram bot token | No | Re-run `channels add`, then restart gateway |
+| OpenAI / Anthropic API keys | No | Update runtime env used by OpenClaw processes |
+| OpenAI Codex OAuth profile | No | Clear auth profile, then relink |
+
+## Connect to your sandbox
+
+
+```python Python
+from e2b import Sandbox
+
+sandbox = Sandbox.connect("", timeout=60 * 30)
+```
+```typescript JavaScript & TypeScript
+import { Sandbox } from 'e2b'
+
+const sandbox = await Sandbox.connect('', {
+ timeoutMs: 60 * 30 * 1000,
+})
+```
+
+
+## Change default model
+
+
+```python Python
+sandbox.commands.run("openclaw config set agents.defaults.model.primary openai/gpt-5.2")
+print(sandbox.commands.run("openclaw models status --json").stdout)
+```
+```typescript JavaScript & TypeScript
+await sandbox.commands.run('openclaw config set agents.defaults.model.primary openai/gpt-5.2')
+const models = await sandbox.commands.run('openclaw models status --json')
+console.log(models.stdout)
+```
+
+
+## Rotate gateway token (no sandbox restart)
+
+After rotation, old URLs with the previous `?token=` stop working.
+
+
+```python Python
+NEW_TOKEN = "replace-me"
+PORT = 18789
+
+rotate = sandbox.commands.run(
+ """bash -lc '
+set -euo pipefail
+pkill -f "openclaw gateway" >/dev/null 2>&1 || true
+nohup openclaw gateway --allow-unconfigured --bind lan --auth token --token "$NEW_TOKEN" --port "$PORT" \
+ > /home/user/openclaw_gateway.log 2>&1 &
+sleep 1
+ss -ltn | grep -q ":$PORT " && echo ready || echo waiting
+'""",
+ envs={"NEW_TOKEN": NEW_TOKEN, "PORT": str(PORT)},
+)
+print(rotate.stdout)
+```
+```typescript JavaScript & TypeScript
+const NEW_TOKEN = 'replace-me'
+const PORT = 18789
+
+const rotate = await sandbox.commands.run(
+ `bash -lc '
+set -euo pipefail
+pkill -f "openclaw gateway" >/dev/null 2>&1 || true
+nohup openclaw gateway --allow-unconfigured --bind lan --auth token --token "$NEW_TOKEN" --port "$PORT" \
+ > /home/user/openclaw_gateway.log 2>&1 &
+sleep 1
+ss -ltn | grep -q ":$PORT " && echo ready || echo waiting
+'`,
+ { envs: { NEW_TOKEN, PORT: String(PORT) } }
+)
+console.log(rotate.stdout)
+```
+
+
+## Update provider keys after deploy
+
+### One-off command
+
+
+```python Python
+one_off = sandbox.commands.run(
+ 'openclaw agent --local --message "hello"',
+ envs={"OPENAI_API_KEY": "sk-..."},
+)
+print(one_off.stdout)
+```
+```typescript JavaScript & TypeScript
+const oneOff = await sandbox.commands.run(
+ 'openclaw agent --local --message "hello"',
+ { envs: { OPENAI_API_KEY: 'sk-...' } }
+)
+console.log(oneOff.stdout)
+```
+
+
+### Persistent runtime env
+
+
+```python Python
+persist = sandbox.commands.run(
+ """bash -lc '
+set -euo pipefail
+cat > /home/user/.openclaw-runtime.env < /home/user/.openclaw-runtime.env <
+
+## Update Telegram bot token
+
+
+```python Python
+telegram = sandbox.commands.run(
+ """bash -lc '
+set -euo pipefail
+openclaw config set plugins.entries.telegram.enabled true
+openclaw channels add --channel telegram --token "$NEW_TELEGRAM_BOT_TOKEN"
+'""",
+ envs={"NEW_TELEGRAM_BOT_TOKEN": "123456:abc"},
+)
+print(telegram.stdout)
+```
+```typescript JavaScript & TypeScript
+const telegram = await sandbox.commands.run(
+ `bash -lc '
+set -euo pipefail
+openclaw config set plugins.entries.telegram.enabled true
+openclaw channels add --channel telegram --token "$NEW_TELEGRAM_BOT_TOKEN"
+'`,
+ { envs: { NEW_TELEGRAM_BOT_TOKEN: '123456:abc' } }
+)
+console.log(telegram.stdout)
+```
+
+
+## Reset Telegram channel + pairing state
+
+
+```python Python
+sandbox.commands.run("openclaw channels remove --channel telegram --delete")
+sandbox.commands.run(
+ 'openclaw channels add --channel telegram --token "$TELEGRAM_BOT_TOKEN"',
+ envs={"TELEGRAM_BOT_TOKEN": "123456:abc"},
+)
+print(sandbox.commands.run("openclaw pairing list --json --channel telegram").stdout)
+```
+```typescript JavaScript & TypeScript
+await sandbox.commands.run('openclaw channels remove --channel telegram --delete')
+await sandbox.commands.run(
+ 'openclaw channels add --channel telegram --token "$TELEGRAM_BOT_TOKEN"',
+ { envs: { TELEGRAM_BOT_TOKEN: '123456:abc' } }
+)
+const pairing = await sandbox.commands.run('openclaw pairing list --json --channel telegram')
+console.log(pairing.stdout)
+```
+
+
+Approve a pending pairing code:
+
+
+```python Python
+PAIRING_CODE = "ABCDEFGH"
+sandbox.commands.run(f"openclaw pairing approve --channel telegram {PAIRING_CODE}")
+```
+```typescript JavaScript & TypeScript
+const PAIRING_CODE = 'ABCDEFGH'
+await sandbox.commands.run(`openclaw pairing approve --channel telegram ${PAIRING_CODE}`)
+```
+
+
+## Reset OpenAI Codex OAuth profile
+
+
+```python Python
+reset = sandbox.commands.run(
+ """python3 - <<'PY'
+import json
+from pathlib import Path
+
+p = Path('/home/user/.openclaw/agents/main/agent/auth-profiles.json')
+raw = json.loads(p.read_text()) if p.exists() else {}
+profiles = raw.get('profiles', {}) if isinstance(raw, dict) else {}
+raw['profiles'] = {
+ k: v for k, v in profiles.items()
+ if not (isinstance(v, dict) and v.get('provider') == 'openai-codex')
+}
+p.parent.mkdir(parents=True, exist_ok=True)
+p.write_text(json.dumps(raw, indent=2) + '\\n')
+print('cleared openai-codex oauth profiles')
+PY"""
+)
+print(reset.stdout)
+```
+```typescript JavaScript & TypeScript
+const reset = await sandbox.commands.run(`python3 - <<'PY'
+import json
+from pathlib import Path
+
+p = Path('/home/user/.openclaw/agents/main/agent/auth-profiles.json')
+raw = json.loads(p.read_text()) if p.exists() else {}
+profiles = raw.get('profiles', {}) if isinstance(raw, dict) else {}
+raw['profiles'] = {
+ k: v for k, v in profiles.items()
+ if not (isinstance(v, dict) and v.get('provider') == 'openai-codex')
+}
+p.parent.mkdir(parents=True, exist_ok=True)
+p.write_text(json.dumps(raw, indent=2) + '\\n')
+print('cleared openai-codex oauth profiles')
+PY`)
+console.log(reset.stdout)
+```
+
+
+Then relink OAuth using the OAuth guide.
+
+## Verify current state
+
+
+```python Python
+print(sandbox.commands.run("openclaw channels list --json").stdout)
+print(sandbox.commands.run("openclaw channels status --json --probe").stdout)
+print(sandbox.commands.run("openclaw pairing list --json --channel telegram").stdout)
+print(sandbox.commands.run("openclaw models status --json").stdout)
+```
+```typescript JavaScript & TypeScript
+console.log((await sandbox.commands.run('openclaw channels list --json')).stdout)
+console.log((await sandbox.commands.run('openclaw channels status --json --probe')).stdout)
+console.log((await sandbox.commands.run('openclaw pairing list --json --channel telegram')).stdout)
+console.log((await sandbox.commands.run('openclaw models status --json')).stdout)
+```
+
+
+## Interactive tasks with PTY (optional)
+
+Use PTY when the command expects interactive input (for example OAuth onboarding).
+
+
+```python Python
+from e2b.sandbox.commands.command_handle import PtySize
+
+terminal = sandbox.pty.create(PtySize(cols=140, rows=40), timeout=0)
+sandbox.pty.send_stdin(terminal.pid, b"openclaw onboard --accept-risk --flow quickstart --mode local --auth-choice openai-codex\\r")
+```
+```typescript JavaScript & TypeScript
+const terminal = await sandbox.pty.create({ cols: 140, rows: 40, timeoutMs: 10 * 60_000, onData: (d) => process.stdout.write(new TextDecoder().decode(d)) })
+await sandbox.pty.sendInput(terminal.pid, new TextEncoder().encode('openclaw onboard --accept-risk --flow quickstart --mode local --auth-choice openai-codex\\r'))
+```
+
+
+## Cleanup
+
+
+```python Python
+sandbox.kill()
+```
+```typescript JavaScript & TypeScript
+await sandbox.kill()
+```
+
+
+## Troubleshooting
+
+- `Unknown channel: telegram`
+ - Telegram channel is not configured yet.
+ - Run `openclaw channels add --channel telegram --token "$TELEGRAM_BOT_TOKEN"`, then retry.
+- `No API key found for provider ...`
+ - Model/provider mismatch.
+ - `openai/...` needs `OPENAI_API_KEY`; `anthropic/...` needs `ANTHROPIC_API_KEY`.
+- Pairing list is empty
+ - Send a fresh message to the bot first.
+
+## Related
+
+
+
+ Run OpenClaw headless in a sandbox
+
+
+ Run OpenClaw's web/channel runtime
+
+
+ Connect and pair Telegram users
+
+
+ Link OpenAI Codex OAuth in a sandbox
+
+
diff --git a/docs/agents/openclaw-gateway.mdx b/docs/agents/openclaw-gateway.mdx
new file mode 100644
index 00000000..f04d65c6
--- /dev/null
+++ b/docs/agents/openclaw-gateway.mdx
@@ -0,0 +1,239 @@
+---
+title: "Deploy OpenClaw"
+description: "Start the OpenClaw gateway in an E2B sandbox, connect your browser, and auto-approve device pairing."
+icon: "globe"
+---
+
+The OpenClaw [gateway](https://docs.openclaw.ai/gateway) serves a web UI for chatting with agents. When exposed over a network it requires **token auth** and **device pairing** — your browser must be approved as a trusted device before it can connect.
+
+This guide starts the gateway inside an E2B sandbox, prints the URL, and programmatically approves your browser.
+
+## Quick start
+
+
+```typescript JavaScript & TypeScript
+import { Sandbox } from 'e2b'
+
+const TOKEN = process.env.OPENCLAW_APP_TOKEN || 'my-gateway-token'
+const PORT = 18789
+
+// 1. Create sandbox
+const sandbox = await Sandbox.create('openclaw', {
+ envs: { OPENAI_API_KEY: process.env.OPENAI_API_KEY },
+ timeoutMs: 3600_000,
+})
+
+// 2. Set the default model
+await sandbox.commands.run('openclaw config set agents.defaults.model.primary openai/gpt-5.2')
+
+// 3. Start the gateway with token auth
+sandbox.commands.run(
+ `openclaw gateway --allow-unconfigured --bind lan --auth token --token ${TOKEN} --port ${PORT}`,
+ { background: true }
+)
+
+// 4. Wait for the gateway to start listening
+for (let i = 0; i < 45; i++) {
+ const probe = await sandbox.commands.run(
+ `bash -lc 'ss -ltn | grep -q ":${PORT} " && echo ready || echo waiting'`
+ )
+ if (probe.stdout.trim() === 'ready') break
+ await new Promise((r) => setTimeout(r, 1000))
+}
+
+const url = `https://${sandbox.getHost(PORT)}/?token=${TOKEN}`
+console.log(`Gateway: ${url}`)
+// Open the URL in your browser before running the next step.
+```
+```python Python
+import os, time
+from e2b import Sandbox
+
+TOKEN = os.environ.get("OPENCLAW_APP_TOKEN", "my-gateway-token")
+PORT = 18789
+
+# 1. Create sandbox
+sandbox = Sandbox.create("openclaw", envs={
+ "OPENAI_API_KEY": os.environ["OPENAI_API_KEY"],
+}, timeout=3600)
+
+# 2. Set the default model
+sandbox.commands.run("openclaw config set agents.defaults.model.primary openai/gpt-5.2")
+
+# 3. Start the gateway with token auth
+sandbox.commands.run(
+ f"openclaw gateway --allow-unconfigured --bind lan --auth token --token {TOKEN} --port {PORT}",
+ background=True,
+)
+
+# 4. Wait for the gateway to start listening
+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}")
+# Open the URL in your browser before running the next step.
+```
+
+
+
+Open the URL in your browser. It will show **"Pairing required"** — that's expected. Run the next step to approve your device.
+
+
+## Approve device pairing
+
+The gateway requires each browser to be approved as a paired device. Run this after opening the URL — it polls for pending requests and approves the first one.
+
+
+```typescript JavaScript & TypeScript
+// Make sure you've opened the gateway URL in your browser before running this.
+
+// 4. Poll for the browser's pending device request and approve it
+for (let i = 0; i < 30; i++) {
+ try {
+ const res = await sandbox.commands.run(
+ `openclaw devices list --json --url ws://127.0.0.1:${PORT} --token ${TOKEN}`
+ )
+ const data = JSON.parse(res.stdout)
+ if (data.pending?.length) {
+ const rid = data.pending[0].requestId
+ await sandbox.commands.run(
+ `openclaw devices approve ${rid} --token ${TOKEN} --url ws://127.0.0.1:${PORT}`
+ )
+ console.log(`Device approved: ${rid}`)
+ break
+ }
+ } catch {}
+ await new Promise((r) => setTimeout(r, 2000))
+}
+```
+```python Python
+import json
+
+# Make sure you've opened the gateway URL in your browser before running this.
+
+# 4. Poll for the browser's pending device request and approve it
+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} --url ws://127.0.0.1:{PORT}"
+ )
+ print(f"Device approved: {rid}")
+ break
+ except Exception:
+ pass
+ time.sleep(2)
+```
+
+
+Once approved, the browser connects and the gateway UI loads.
+
+## How it works
+
+| Step | What happens |
+|------|-------------|
+| `--bind lan` | Gateway listens on `0.0.0.0` so E2B can proxy it |
+| `--auth token` | Requires `?token=` on the URL for HTTP and WebSocket auth |
+| Browser opens URL | Gateway serves the UI, browser opens a WebSocket |
+| `code=1008 pairing required` | Gateway closes the WebSocket until the device is approved |
+| `devices approve` | Approves the browser's device fingerprint |
+| Browser reconnects | WebSocket connects successfully, UI is live |
+
+## Gateway flags reference
+
+| Flag | Purpose |
+|------|---------|
+| `--allow-unconfigured` | Start without a full config file |
+| `--bind lan` | Bind to `0.0.0.0` (required for E2B port proxying) |
+| `--auth token` | Enable token-based authentication |
+| `--token ` | The auth token (passed as `?token=` in the URL) |
+| `--port ` | Gateway listen port (default: `18789`) |
+
+## How to restart the gateway
+
+Use this when the gateway is already running and you want a clean restart (for example, after changing model or env settings).
+
+
+We can't use the `openclaw gateway restart` command here. Some SDK environments cannot target a specific Unix user in `commands.run`. The commands below use the default command user context.
+
+
+
+```typescript JavaScript & TypeScript
+const TOKEN = process.env.OPENCLAW_APP_TOKEN || 'my-gateway-token'
+const PORT = 18789
+
+// 1) Kill existing gateway processes if present
+await 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" >/dev/null 2>&1 || true; done; done'`
+)
+await new Promise((r) => setTimeout(r, 1000))
+
+// 2) Start gateway again
+await sandbox.commands.run(
+ `openclaw gateway --allow-unconfigured --bind lan --auth token --token ${TOKEN} --port ${PORT}`,
+ { background: true }
+)
+
+// 3) Wait for listening socket
+for (let i = 0; i < 45; i++) {
+ const probe = await sandbox.commands.run(
+ `bash -lc 'ss -ltn | grep -q ":${PORT} " && echo ready || echo waiting'`
+ )
+ if (probe.stdout.trim() === 'ready') break
+ await new Promise((r) => setTimeout(r, 1000))
+}
+```
+```python Python
+import os, time
+
+TOKEN = os.environ.get("OPENCLAW_APP_TOKEN", "my-gateway-token")
+PORT = 18789
+
+# 1) Kill existing gateway processes if present
+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" >/dev/null 2>&1 || true
+done
+done'"""
+)
+time.sleep(1)
+
+# 2) Start gateway again
+sandbox.commands.run(
+ f"openclaw gateway --allow-unconfigured --bind lan --auth token --token {TOKEN} --port {PORT}",
+ background=True,
+)
+
+# 3) Wait for listening socket
+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)
+```
+
+
+## Related
+
+
+
+ Connect OpenClaw to Telegram and approve pairing
+
+
+ Connect OpenClaw to Slack and approve pairing
+
+
diff --git a/docs/agents/openclaw-oauth.mdx b/docs/agents/openclaw-oauth.mdx
new file mode 100644
index 00000000..6fdcd991
--- /dev/null
+++ b/docs/agents/openclaw-oauth.mdx
@@ -0,0 +1,315 @@
+---
+title: "OpenClaw Codex OAuth"
+description: "Set up OpenAI Codex OAuth for OpenClaw using interactive PTY in an E2B sandbox."
+icon: "key"
+---
+
+OpenClaw can authenticate with OpenAI Codex via OAuth instead of an API key.
+
+This page uses a PTY-first flow, which works on raw OpenClaw sandboxes.
+
+## Prerequisites
+
+- App started from setup.
+- `E2B_API_KEY` in your local environment.
+- OpenAI account with Codex access.
+
+## End-to-end PTY flow
+
+### Connect to your sandbox
+
+
+```python Python
+import json
+import re
+import threading
+import time
+from e2b import Sandbox
+from e2b.sandbox.commands.command_handle import CommandExitException, PtySize
+
+sandbox = Sandbox.connect("", timeout=60 * 30)
+
+pty_chunks = []
+terminal = None
+thread = None
+
+def on_pty(data: bytes):
+ text = data.decode("utf-8", errors="replace")
+ print(text, end="")
+ pty_chunks.append(text)
+
+def pty_send_line(text: str):
+ # For OpenClaw TUI prompts, carriage return is more reliable than newline.
+ sandbox.pty.send_stdin(terminal.pid, (text + "\r").encode())
+```
+```typescript JavaScript & TypeScript
+import { Sandbox } from 'e2b'
+import * as readline from 'node:readline/promises'
+import { stdin as input, stdout as output } from 'node:process'
+
+const sandbox = await Sandbox.connect('', {
+ timeoutMs: 60 * 30 * 1000,
+})
+
+let ptyOutput = ''
+const decoder = new TextDecoder()
+const encoder = new TextEncoder()
+let terminal
+let waitPromise
+
+const rl = readline.createInterface({ input, output })
+const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
+
+async function sendLine(line) {
+ await sandbox.pty.sendInput(terminal.pid, encoder.encode(`${line}\r`))
+}
+```
+
+
+### Start onboarding in PTY
+
+
+```python Python
+terminal = sandbox.pty.create(
+ PtySize(cols=140, rows=40),
+ timeout=0, # keep PTY open while you complete browser login
+)
+
+thread = threading.Thread(
+ target=lambda: terminal.wait(on_pty=on_pty),
+ daemon=True,
+)
+thread.start()
+
+onboard_cmd = (
+ "openclaw onboard --accept-risk --flow quickstart --mode local "
+ "--auth-choice openai-codex --skip-ui --skip-channels --skip-skills --skip-health"
+)
+pty_send_line(onboard_cmd)
+```
+```typescript JavaScript & TypeScript
+terminal = await sandbox.pty.create({
+ cols: 140,
+ rows: 40,
+ timeoutMs: 10 * 60_000,
+ onData: (data) => {
+ const text = decoder.decode(data)
+ process.stdout.write(text)
+ ptyOutput += text
+ },
+})
+
+waitPromise = terminal.wait()
+
+await sendLine(
+ 'openclaw onboard --accept-risk --flow quickstart --mode local --auth-choice openai-codex --skip-ui --skip-channels --skip-skills --skip-health'
+)
+```
+
+
+### Extract auth URL and open it in your local browser
+
+
+```python Python
+ansi_re = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]")
+oauth_re = re.compile(r"https://auth\.openai\.com/oauth/authorize[^\s\"']+")
+
+auth_url = None
+for _ in range(60):
+ cleaned = ansi_re.sub("", "".join(pty_chunks).replace("\r", "\n"))
+ match = oauth_re.search(cleaned)
+ if match:
+ auth_url = match.group(0)
+ break
+ time.sleep(1)
+
+if not auth_url:
+ raise RuntimeError("OAuth URL not found in PTY output")
+
+print("Open this URL in your LOCAL browser:\n")
+print(auth_url)
+```
+```typescript JavaScript & TypeScript
+const ansiRe = /\x1b\[[0-9;?]*[ -/]*[@-~]/g
+const oauthRe = /https:\/\/auth\.openai\.com\/oauth\/authorize[^\s"']+/
+
+let authUrl = null
+for (let i = 0; i < 60; i++) {
+ const cleaned = ptyOutput.replace(/\r/g, '\n').replace(ansiRe, '')
+ const match = cleaned.match(oauthRe)
+ if (match) {
+ authUrl = match[0]
+ break
+ }
+ await sleep(1000)
+}
+
+if (!authUrl) throw new Error('OAuth URL not found in PTY output')
+
+console.log('\nOpen this URL in your LOCAL browser:\n')
+console.log(authUrl)
+```
+
+
+After browser sign-in, copy the localhost callback URL:
+
+`http://localhost:1455/auth/callback?code=...&state=...`
+
+### Submit callback back into PTY
+
+
+```python Python
+callback_url = input("\nPaste localhost callback URL: ").strip()
+pty_send_line(callback_url)
+
+# Wait briefly so OpenClaw can process the callback, then exit PTY shell.
+time.sleep(2)
+pty_send_line("exit")
+
+try:
+ thread.join(timeout=30)
+except CommandExitException:
+ pass
+```
+```typescript JavaScript & TypeScript
+const callbackUrl = (await rl.question('\nPaste localhost callback URL: ')).trim()
+await sendLine(callbackUrl)
+
+await sleep(2000)
+await sendLine('exit')
+
+await Promise.race([
+ waitPromise,
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('PTY wait timeout after 30s')), 30_000)
+ ),
+]).catch((e) => console.warn(String(e)))
+
+rl.close()
+```
+
+
+### Verify OAuth link and run a smoke command
+
+
+```python Python
+auth_check = sandbox.commands.run(
+ """python3 - <<'PY'
+import json
+from pathlib import Path
+p = Path('/home/user/.openclaw/agents/main/agent/auth-profiles.json')
+raw = json.loads(p.read_text()) if p.exists() else {}
+profiles = raw.get('profiles', {}) if isinstance(raw, dict) else {}
+ok = any(
+ isinstance(v, dict) and v.get('provider') == 'openai-codex' and v.get('refresh')
+ for v in profiles.values()
+)
+print('openai_codex_oauth_linked=' + ('true' if ok else 'false'))
+PY"""
+)
+print(auth_check.stdout)
+
+sandbox.commands.run(
+ "openclaw config set agents.defaults.model.primary openai-codex/gpt-5.2-codex"
+)
+
+smoke = sandbox.commands.run(
+ 'openclaw agent --session-id main --thinking low --message "Say exactly: oauth-ok"'
+)
+print(smoke.stdout)
+```
+```typescript JavaScript & TypeScript
+const authRaw = await sandbox.commands.run(
+ "cat /home/user/.openclaw/agents/main/agent/auth-profiles.json 2>/dev/null || echo '{}'"
+)
+const authStore = JSON.parse(authRaw.stdout || '{}')
+const profiles = authStore?.profiles ?? {}
+const linked = Object.values(profiles).some(
+ (p) => p?.provider === 'openai-codex' && p?.refresh
+)
+console.log({ openai_codex_oauth_linked: linked })
+if (!linked) throw new Error('openai-codex OAuth profile not linked')
+
+await sandbox.commands.run(
+ 'openclaw config set agents.defaults.model.primary openai-codex/gpt-5.2-codex'
+)
+
+const smoke = await sandbox.commands.run(
+ 'openclaw agent --session-id main --thinking low --message "Say exactly: oauth-ok"'
+)
+console.log(smoke.stdout)
+```
+
+
+## Cleanup
+
+
+```python Python
+sandbox.kill()
+```
+```typescript JavaScript & TypeScript
+await sandbox.kill()
+```
+
+
+## Optional: reset and relink OAuth
+
+
+```python Python
+reset = sandbox.commands.run(
+ """python3 - <<'PY'
+import json
+from pathlib import Path
+
+p = Path('/home/user/.openclaw/agents/main/agent/auth-profiles.json')
+raw = json.loads(p.read_text()) if p.exists() else {}
+profiles = raw.get('profiles', {}) if isinstance(raw, dict) else {}
+raw['profiles'] = {
+ k: v for k, v in profiles.items()
+ if not (isinstance(v, dict) and v.get('provider') == 'openai-codex')
+}
+p.parent.mkdir(parents=True, exist_ok=True)
+p.write_text(json.dumps(raw, indent=2) + '\\n')
+print('cleared openai-codex oauth profiles')
+PY"""
+)
+print(reset.stdout)
+```
+```typescript JavaScript & TypeScript
+const reset = await sandbox.commands.run(`python3 - <<'PY'
+import json
+from pathlib import Path
+
+p = Path('/home/user/.openclaw/agents/main/agent/auth-profiles.json')
+raw = json.loads(p.read_text()) if p.exists() else {}
+profiles = raw.get('profiles', {}) if isinstance(raw, dict) else {}
+raw['profiles'] = {
+ k: v for k, v in profiles.items()
+ if not (isinstance(v, dict) and v.get('provider') == 'openai-codex')
+}
+p.parent.mkdir(parents=True, exist_ok=True)
+p.write_text(json.dumps(raw, indent=2) + '\\n')
+print('cleared openai-codex oauth profiles')
+PY`)
+console.log(reset.stdout)
+```
+
+
+After reset, rerun onboarding and verification.
+
+## Related
+
+
+
+ Run OpenClaw headless in a sandbox
+
+
+ Run OpenClaw's web gateway with token auth
+
+
+ Connect OpenClaw to Telegram and approve pairing
+
+
+ Change models, tokens, and channels after deploy
+
+
diff --git a/docs/agents/openclaw-slack.mdx b/docs/agents/openclaw-slack.mdx
new file mode 100644
index 00000000..8b564a8c
--- /dev/null
+++ b/docs/agents/openclaw-slack.mdx
@@ -0,0 +1,182 @@
+---
+title: "OpenClaw Slack"
+description: "Connect OpenClaw to Slack in an E2B sandbox, approve pairing, and chat through your Slack app."
+icon: "message-circle"
+---
+
+OpenClaw supports Slack as a chat channel. Using E2B you can run OpenClaw in a sandbox, attach Slack tokens, and approve user pairing from the terminal.
+
+Official OpenClaw Slack channel docs: [docs.openclaw.ai/channels/slack](https://docs.openclaw.ai/channels/slack)
+
+## Prerequisites
+
+- Slack app tokens from the same Slack app:
+ - `SLACK_APP_TOKEN` (`xapp-...`, Socket Mode)
+ - `SLACK_BOT_TOKEN` (`xoxb-...`)
+- `OPENAI_API_KEY` (this guide uses `openai/gpt-5.2`)
+- E2B API key configured locally
+
+## Quick start
+
+
+```typescript JavaScript & TypeScript
+import { Sandbox } from 'e2b'
+
+const GATEWAY_PORT = 18789
+const GATEWAY_TOKEN = process.env.OPENCLAW_APP_TOKEN || 'my-openclaw-token'
+
+const sandbox = await Sandbox.create('openclaw', {
+ envs: {
+ OPENAI_API_KEY: process.env.OPENAI_API_KEY,
+ SLACK_APP_TOKEN: process.env.SLACK_APP_TOKEN,
+ SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN,
+ },
+ timeoutMs: 3600_000,
+})
+
+await sandbox.commands.run('openclaw config set agents.defaults.model.primary openai/gpt-5.2')
+await sandbox.commands.run('openclaw config set plugins.entries.slack.enabled true')
+await sandbox.commands.run(
+ 'openclaw channels add --channel slack --app-token "$SLACK_APP_TOKEN" --bot-token "$SLACK_BOT_TOKEN"'
+)
+
+await sandbox.commands.run(
+ `openclaw gateway --allow-unconfigured --bind lan --auth token --token ${GATEWAY_TOKEN} --port ${GATEWAY_PORT}`,
+ { background: true }
+)
+
+for (let i = 0; i < 45; i++) {
+ const probe = await sandbox.commands.run(
+ `bash -lc 'ss -ltn | grep -q ":${GATEWAY_PORT} " && echo ready || echo waiting'`
+ )
+ if (probe.stdout.trim() === 'ready') break
+ await new Promise((r) => setTimeout(r, 1000))
+}
+```
+```python Python
+import os
+import time
+from e2b import Sandbox
+
+GATEWAY_PORT = 18789
+GATEWAY_TOKEN = os.environ.get("OPENCLAW_APP_TOKEN", "my-openclaw-token")
+
+sandbox = Sandbox.create("openclaw", envs={
+ "OPENAI_API_KEY": os.environ["OPENAI_API_KEY"],
+ "SLACK_APP_TOKEN": os.environ["SLACK_APP_TOKEN"],
+ "SLACK_BOT_TOKEN": os.environ["SLACK_BOT_TOKEN"],
+}, timeout=3600)
+
+sandbox.commands.run("openclaw config set agents.defaults.model.primary openai/gpt-5.2")
+sandbox.commands.run("openclaw config set plugins.entries.slack.enabled true")
+sandbox.commands.run(
+ 'openclaw channels add --channel slack --app-token "$SLACK_APP_TOKEN" --bot-token "$SLACK_BOT_TOKEN"'
+)
+
+sandbox.commands.run(
+ f"openclaw gateway --allow-unconfigured --bind lan --auth token --token {GATEWAY_TOKEN} --port {GATEWAY_PORT}",
+ background=True,
+)
+
+for _ in range(45):
+ probe = sandbox.commands.run(
+ f"bash -lc 'ss -ltn | grep -q \":{GATEWAY_PORT} \" && echo ready || echo waiting'"
+ )
+ if probe.stdout.strip() == "ready":
+ break
+ time.sleep(1)
+```
+
+
+
+For Slack setup, you do **not** need to open the gateway URL in a browser. The gateway process is used as a long-running channel runtime.
+
+
+## Configure your Slack app
+
+Before testing messages, make sure Slack app settings are complete:
+
+- Socket Mode enabled (`xapp-...` token with `connections:write`)
+- Bot scopes include:
+ - `app_mentions:read`
+ - `chat:write`
+ - `channels:history`
+ - `im:history` (for DMs)
+- Event Subscriptions enabled with bot events:
+ - `app_mention`
+ - `message.im` (for DMs)
+- App reinstalled to workspace after scope/event changes
+- Bot invited to target channel (`/invite @your-bot`)
+
+## Pair your Slack user
+
+On first message, Slack may return:
+
+```text
+OpenClaw: access not configured.
+Your Slack user id: ...
+Pairing code: XXXXXXXX
+Ask the bot owner to approve with:
+openclaw pairing approve slack XXXXXXXX
+```
+
+Approve that code:
+
+
+```typescript JavaScript & TypeScript
+const PAIRING_CODE = 'XXXXXXXX' // from Slack
+await sandbox.commands.run(`openclaw pairing approve slack ${PAIRING_CODE}`)
+```
+```python Python
+PAIRING_CODE = "XXXXXXXX" # from Slack
+sandbox.commands.run(f"openclaw pairing approve slack {PAIRING_CODE}")
+```
+
+
+## Verify channel status
+
+
+```typescript JavaScript & TypeScript
+const channels = await sandbox.commands.run('openclaw channels list --json')
+const status = await sandbox.commands.run('openclaw channels status --json --probe')
+const logs = await sandbox.commands.run('openclaw channels logs --channel slack --lines 200')
+
+console.log(channels.stdout)
+console.log(status.stdout) // may print text instead of JSON when gateway is unreachable
+console.log(logs.stdout)
+```
+```python Python
+channels = sandbox.commands.run("openclaw channels list --json")
+status = sandbox.commands.run("openclaw channels status --json --probe")
+logs = sandbox.commands.run("openclaw channels logs --channel slack --lines 200")
+
+print(channels.stdout)
+print(status.stdout) # may print text instead of JSON when gateway is unreachable
+print(logs.stdout)
+```
+
+
+Healthy Slack logs should show a connected provider, then incoming events and replies after you message the bot.
+
+## Troubleshooting
+
+- Slack logs show only `slack socket mode connected` and nothing else
+ - Slack is connected but events are not being delivered.
+ - Recheck Event Subscriptions (`app_mention`, `message.im`), reinstall app, and invite bot to channel.
+- `OpenClaw: access not configured`
+ - Pairing is required for this Slack user. Approve the pairing code.
+- `No API key found for provider ...`
+ - This guide uses `openai/gpt-5.2`. Set `OPENAI_API_KEY` in sandbox envs.
+- `Sending messages to this app has been turned off`
+ - In Slack App Home, enable users sending messages to the app, then reinstall.
+
+## Related
+
+
+
+ Run OpenClaw's web gateway with token auth
+
+
+ Connect OpenClaw to Telegram and approve pairing
+
+
diff --git a/docs/agents/openclaw-telegram.mdx b/docs/agents/openclaw-telegram.mdx
new file mode 100644
index 00000000..b40999b5
--- /dev/null
+++ b/docs/agents/openclaw-telegram.mdx
@@ -0,0 +1,195 @@
+---
+title: "OpenClaw Telegram"
+description: "Connect OpenClaw to Telegram in an E2B sandbox, approve pairing, and chat through your bot."
+icon: "paper-plane"
+---
+
+OpenClaw supports Telegram as a chat channel. In E2B you can run OpenClaw in a sandbox, attach your bot token, and approve user pairing from the terminal.
+
+This guide covers the working flow we used:
+
+1. Start OpenClaw in a sandbox.
+2. Enable the Telegram plugin.
+3. Add Telegram channel credentials.
+4. Start the channel runtime in background.
+5. Approve Telegram pairing.
+
+## Prerequisites
+
+- A Telegram bot token from [@BotFather](https://t.me/BotFather).
+- An OpenAI API key for the OpenClaw model.
+- E2B API key configured locally.
+
+## Quick start
+
+
+```typescript JavaScript & TypeScript
+import { Sandbox } from 'e2b'
+
+const GATEWAY_PORT = 18789
+const GATEWAY_TOKEN = process.env.OPENCLAW_APP_TOKEN || 'my-openclaw-token'
+
+const sandbox = await Sandbox.create('openclaw', {
+ envs: {
+ OPENAI_API_KEY: process.env.OPENAI_API_KEY,
+ TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN,
+ },
+ timeoutMs: 3600_000,
+})
+
+await sandbox.commands.run('openclaw config set agents.defaults.model.primary openai/gpt-5.2')
+
+// Enable the Telegram plugin (required before adding the channel)
+await sandbox.commands.run('openclaw config set plugins.entries.telegram.enabled true')
+await sandbox.commands.run('openclaw channels add --channel telegram --token "$TELEGRAM_BOT_TOKEN"')
+
+await sandbox.commands.run(
+ `openclaw gateway --allow-unconfigured --bind lan --auth token --token ${GATEWAY_TOKEN} --port ${GATEWAY_PORT}`,
+ { background: true }
+)
+
+for (let i = 0; i < 45; i++) {
+ const probe = await sandbox.commands.run(
+ `bash -lc 'ss -ltn | grep -q ":${GATEWAY_PORT} " && echo ready || echo waiting'`
+ )
+ if (probe.stdout.trim() === 'ready') break
+ await new Promise((r) => setTimeout(r, 1000))
+}
+```
+```python Python
+import os
+import time
+from e2b import Sandbox
+
+GATEWAY_PORT = 18789
+GATEWAY_TOKEN = os.environ.get("OPENCLAW_APP_TOKEN", "my-openclaw-token")
+
+sandbox = Sandbox.create("openclaw", envs={
+ "OPENAI_API_KEY": os.environ["OPENAI_API_KEY"],
+ "TELEGRAM_BOT_TOKEN": os.environ["TELEGRAM_BOT_TOKEN"],
+}, timeout=3600)
+
+sandbox.commands.run("openclaw config set agents.defaults.model.primary openai/gpt-5.2")
+
+# Enable the Telegram plugin (required before adding the channel)
+sandbox.commands.run("openclaw config set plugins.entries.telegram.enabled true")
+sandbox.commands.run('openclaw channels add --channel telegram --token "$TELEGRAM_BOT_TOKEN"')
+
+sandbox.commands.run(
+ f"openclaw gateway --allow-unconfigured --bind lan --auth token --token {GATEWAY_TOKEN} --port {GATEWAY_PORT}",
+ background=True,
+)
+
+for _ in range(45):
+ probe = sandbox.commands.run(
+ f"bash -lc 'ss -ltn | grep -q \":{GATEWAY_PORT} \" && echo ready || echo waiting'"
+ )
+ if probe.stdout.strip() == "ready":
+ break
+ time.sleep(1)
+```
+
+
+
+For Telegram setup, you do **not** need to open the gateway URL in a browser. The gateway process is used here as a long-running channel runtime.
+
+
+## Pair your Telegram user
+
+1. Open your bot in Telegram and send a message (for example: `hi`).
+2. Telegram will return a pairing prompt similar to:
+
+```text
+OpenClaw: access not configured.
+
+Your Telegram user id: ...
+Pairing code: XXXXXXXX
+
+Ask the bot owner to approve with:
+openclaw pairing approve telegram XXXXXXXX
+```
+
+3. Approve that pairing code via `sandbox.commands.run(...)`:
+
+
+```typescript JavaScript & TypeScript
+const PAIRING_CODE = 'XXXXXXXX' // from Telegram
+
+await sandbox.commands.run(
+ `openclaw pairing approve --channel telegram ${PAIRING_CODE}`
+)
+```
+```python Python
+PAIRING_CODE = "XXXXXXXX" # from Telegram
+
+sandbox.commands.run(
+ f"openclaw pairing approve --channel telegram {PAIRING_CODE}"
+)
+```
+
+
+`openclaw pairing approve telegram ` also works if you prefer that form.
+
+## Verify channel status
+
+
+```typescript JavaScript & TypeScript
+const channels = await sandbox.commands.run('openclaw channels list --json')
+const status = await sandbox.commands.run('openclaw channels status --json --probe')
+const pairing = await sandbox.commands.run('openclaw pairing list --json --channel telegram')
+
+console.log(JSON.parse(channels.stdout))
+console.log(JSON.parse(status.stdout))
+console.log(JSON.parse(pairing.stdout))
+```
+```python Python
+import json
+
+channels = sandbox.commands.run("openclaw channels list --json")
+status = sandbox.commands.run("openclaw channels status --json --probe")
+pairing = sandbox.commands.run("openclaw pairing list --json --channel telegram")
+
+print(json.loads(channels.stdout))
+print(json.loads(status.stdout))
+print(json.loads(pairing.stdout))
+```
+
+
+If you need logs from channel handlers:
+
+
+```typescript JavaScript & TypeScript
+const logs = await sandbox.commands.run(
+ 'openclaw channels logs --channel telegram --lines 200'
+)
+console.log(logs.stdout)
+```
+```python Python
+logs = sandbox.commands.run(
+ "openclaw channels logs --channel telegram --lines 200"
+)
+print(logs.stdout)
+```
+
+
+## Troubleshooting
+
+- `Unknown channel: telegram`
+ - The Telegram plugin is not enabled. Run `openclaw config set plugins.entries.telegram.enabled true` before adding the channel.
+- `OpenClaw: access not configured`
+ - Pairing has not been approved yet. Run `openclaw pairing approve ...`.
+- `No API key found for provider ...`
+ - This guide uses `openai/gpt-5.2`. Set `OPENAI_API_KEY` in sandbox envs.
+- No pending pairing requests from `pairing list`
+ - Send a fresh message to the bot first, then retry `pairing list --channel telegram`.
+
+## Related
+
+
+
+ Run OpenClaw's web gateway with token auth
+
+
+ Connect OpenClaw to Slack and approve pairing
+
+
diff --git a/docs/agents/openclaw.mdx b/docs/agents/openclaw.mdx
index 374a4509..b55e1c4f 100644
--- a/docs/agents/openclaw.mdx
+++ b/docs/agents/openclaw.mdx
@@ -1,7 +1,7 @@
---
-title: "OpenClaw"
+title: "Customize OpenClaw"
description: "Run OpenClaw in a secure E2B sandbox with full filesystem, terminal, and git access."
-icon: "/images/icons/openclaw.svg"
+icon: "wand-magic-sparkles"
---
[OpenClaw](https://openclaw.ai) is an open-source personal AI assistant that supports multiple LLM providers and messaging channels. E2B provides a pre-built `openclaw` template with OpenClaw already installed.
@@ -382,50 +382,6 @@ sandbox.kill()
```
-## Launch the gateway
-
-OpenClaw has a built-in [web UI and chat interface](https://openclaw.ai) served by its gateway. Start it inside a sandbox and connect from your browser.
-
-
-This sandbox is created with a 10-minute timeout and auto-pause enabled — after 10 minutes of inactivity it pauses and can be resumed later. See [Sandbox Persistence](/docs/sandbox/persistence) and [Sandbox Lifecycle](/docs/sandbox) for more details.
-
-
-
-```typescript JavaScript & TypeScript
-import { Sandbox } from 'e2b'
-
-const sandbox = await Sandbox.betaCreate('openclaw', {
- envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY },
- autoPause: true,
- timeoutMs: 10 * 60 * 1000,
-})
-
-// Start the gateway
-sandbox.commands.run('openclaw gateway --port 18789 --verbose')
-
-const host = sandbox.getHost(18789)
-const url = `https://${host}`
-console.log(`OpenClaw Gateway: ${url}`)
-console.log(`Sandbox ID: ${sandbox.sandboxId}`)
-```
-```python Python
-import os
-from e2b import Sandbox
-
-sandbox = Sandbox.beta_create("openclaw", envs={
- "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"],
-}, auto_pause=True, timeout=10 * 60)
-
-# Start the gateway
-sandbox.commands.run("openclaw gateway --port 18789 --verbose")
-
-host = sandbox.get_host(18789)
-url = f"https://{host}"
-print(f"OpenClaw Gateway: {url}")
-print(f"Sandbox ID: {sandbox.sandbox_id}")
-```
-
-
## Build a custom template
If you need to customize the environment (e.g. pre-install dependencies, add config files), build your own template on top of the pre-built `openclaw` template.
@@ -495,16 +451,22 @@ python build.py
```
-## Related guides
-
-
-
- Auto-pause, resume, and manage sandbox lifecycle
-
-
- Clone repos, manage branches, and push changes
-
-
- Connect to the sandbox via SSH for interactive sessions
-
-
+## Related
+
+
+
+ Run OpenClaw's web gateway with token auth
+
+
+ Connect OpenClaw to Telegram and approve pairing
+
+
+ Connect OpenClaw to Slack and approve pairing
+
+
+ Link OpenAI Codex OAuth in a sandbox
+
+
+ Change models, tokens, and channel settings after deploy
+
+