Screenshot a page an agent built in a sandbox
The model writes an HTML page, a sandbox with no network serves it on a declared port, and render captures the sandbox's preview URL.
An agent that builds a page should look at it. Here the model writes one self-contained HTML file (an uptime page for
a fictional service), a sandbox serves it with python3 -m http.server on port 8080, and render captures it through
the sandbox's preview URL, in a browser frame on a background. The sandbox declares the port when it is created and
has egress: deny: serving a file needs no network. POST /v1/sandboxes/{id}/ports/8080/url mints a URL on
usercontent.b3os.org that works for 10 minutes, with no cookie or key; render loads it like any other page.
Verified 2026-11-01: this recipe's last run passed against https://beta-api.b3os.org, label live-recorded (recorded from production), from commit 771a9508. The recipe harness runs it again, and a failed run takes this page out of the cookbook.
Run it
bashAPI=https://beta-api.b3os.orgAUTH="Authorization: Bearer $B3OS_API_KEY"curl -s "$API/v1/infer/chat/completions" -H "$AUTH" -H "Content-Type: application/json" -d '{ "model": "b3os/qwen3.5-9b", "temperature": 0, "max_tokens": 3500, "chat_template_kwargs": {"enable_thinking": false}, "messages": [ {"role": "system", "content": "You write one complete, self-contained HTML5 file: inline CSS, no JavaScript, no external URLs. Output only the HTML."}, {"role": "user", "content": "An uptime page for a fictional service called Example Cloud with four components and their 90-day uptime."}]}' \| jq -r '.choices[0].message.content' | sed '/^```/d' > index.htmlSBX=$(curl -s "$API/v1/sandboxes" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"template": "base", "timeout_s": 300, "ports": [{"port": 8080}], "egress": {"mode": "deny"}}' | jq -r .id)curl -s -X PUT "$API/v1/sandboxes/$SBX/files?path=/workspace/site/index.html&mkdir=1" -H "$AUTH" \ -H "Content-Type: application/octet-stream" --data-binary @index.html > /dev/nullcurl -s "$API/v1/sandboxes/$SBX/exec" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"cmd": "python3 -m http.server 8080 --bind 0.0.0.0 --directory /workspace/site", "background": true}' > /dev/nullsleep 1PREVIEW=$(curl -s -X POST "$API/v1/sandboxes/$SBX/ports/8080/url" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"ttl_s": 600}' | jq -r .url)curl -s "$API/v1/render/screenshots" -H "$AUTH" -H "Content-Type: application/json" \ -d "$(jq -n --arg url "$PREVIEW" '{url: $url, viewport: {width: 1280, height: 720}, style: {background: "b3os-dark", frame: "browser-dark"}, format: "webp"}')" \ | jq -r '.artifacts[0].url' | xargs curl -s -o preview.webpcurl -s -X DELETE "$API/v1/sandboxes/$SBX" -H "$AUTH" | jq .status
tsimport { writeFileSync } from "node:fs";const API = "https://beta-api.b3os.org";const auth = { authorization: `Bearer ${process.env.B3OS_API_KEY}` };const json = { ...auth, "content-type": "application/json" };const post = async (path: string, body: unknown): Promise<any> => (await fetch(`${API}${path}`, { method: "POST", headers: json, body: JSON.stringify(body) })).json();const chat = await post("/v1/infer/chat/completions", { model: "b3os/qwen3.5-9b", temperature: 0, max_tokens: 3500, chat_template_kwargs: { enable_thinking: false }, messages: [ { role: "system", content: "You write one complete, self-contained HTML5 file: inline CSS, no JavaScript, no external URLs. Output only the HTML." }, { role: "user", content: "An uptime page for a fictional service called Example Cloud with four components and their 90-day uptime." }, ],});const html = chat.choices[0].message.content.replace(/^```\w*\n?|```$/gm, "");const sbx = await post("/v1/sandboxes", { template: "base", timeout_s: 300, ports: [{ port: 8080 }], egress: { mode: "deny" } });try { await fetch(`${API}/v1/sandboxes/${sbx.id}/files?path=/workspace/site/index.html&mkdir=1`, { method: "PUT", headers: { ...auth, "content-type": "application/octet-stream" }, body: html, }); await post(`/v1/sandboxes/${sbx.id}/exec`, { cmd: "python3 -m http.server 8080 --bind 0.0.0.0 --directory /workspace/site", background: true, }); await new Promise((r) => setTimeout(r, 1000)); const preview = await post(`/v1/sandboxes/${sbx.id}/ports/8080/url`, { ttl_s: 600 }); const shot = await post("/v1/render/screenshots", { url: preview.url, viewport: { width: 1280, height: 720 }, style: { background: "b3os-dark", frame: "browser-dark" }, format: "webp", }); writeFileSync("preview.webp", Buffer.from(await (await fetch(shot.artifacts[0].url)).arrayBuffer()));} finally { await fetch(`${API}/v1/sandboxes/${sbx.id}`, { method: "DELETE", headers: auth });}
pythonimport osimport reimport timeimport requestsAPI = "https://beta-api.b3os.org"s = requests.Session()s.headers["Authorization"] = f"Bearer {os.environ['B3OS_API_KEY']}"chat = s.post(f"{API}/v1/infer/chat/completions", json={ "model": "b3os/qwen3.5-9b", "temperature": 0, "max_tokens": 3500, "chat_template_kwargs": {"enable_thinking": False}, "messages": [ {"role": "system", "content": "You write one complete, self-contained HTML5 file: inline CSS, no JavaScript, " "no external URLs. Output only the HTML."}, {"role": "user", "content": "An uptime page for a fictional service called Example Cloud with four components " "and their 90-day uptime."}, ],}).json()html = re.sub(r"^```\w*\n?|```$", "", chat["choices"][0]["message"]["content"], flags=re.M)sbx = s.post(f"{API}/v1/sandboxes", json={ "template": "base", "timeout_s": 300, "ports": [{"port": 8080}], "egress": {"mode": "deny"},}).json()try: s.put(f"{API}/v1/sandboxes/{sbx['id']}/files", params={"path": "/workspace/site/index.html", "mkdir": 1}, data=html.encode(), headers={"Content-Type": "application/octet-stream"}) s.post(f"{API}/v1/sandboxes/{sbx['id']}/exec", json={ "cmd": "python3 -m http.server 8080 --bind 0.0.0.0 --directory /workspace/site", "background": True}) time.sleep(1) preview = s.post(f"{API}/v1/sandboxes/{sbx['id']}/ports/8080/url", json={"ttl_s": 600}).json() shot = s.post(f"{API}/v1/render/screenshots", json={ "url": preview["url"], "viewport": {"width": 1280, "height": 720}, "style": {"background": "b3os-dark", "frame": "browser-dark"}, "format": "webp", }).json() with open("preview.webp", "wb") as f: f.write(requests.get(shot["artifacts"][0]["url"]).content)finally: s.delete(f"{API}/v1/sandboxes/{sbx['id']}")
On the remote B3OS MCP server, ask your agent:
textWrite a self-contained HTML uptime page for a fictional service called Example Cloud. Create a base sandbox with port8080 declared and egress deny, write the page to /workspace/site/index.html, serve that folder with`python3 -m http.server 8080` in the background, get a preview URL for port 8080, take a screenshot of it in a darkbrowser frame, show it to me, then delete the sandbox.
The agent calls sandbox_create, sandbox_write_file, sandbox_exec, sandbox_port_url, render_screenshot and
sandbox_delete.
The page changes with the model's output, so the image below is the last green run's, drawn in production from a
page the model wrote in that run. A preview URL carries no credential: anyone who has it reaches the port until it
expires, so keep ttl_s short and the port private.
Owner: Docs. Products: Docs, Infer, Sandboxes, Render.
Output of the last green run
bash$ node run.mjs# exit 0
json{ "model": "b3os/qwen3.5-9b", "html_bytes": 7133, "preview_host": "usercontent.b3os.org", "render_status": "succeeded", "image": "https://beta.b3os.org/gallery/recipes/sandbox-preview-screenshot/ec299f0f4cc288e0.webp", "image_bytes": 34686, "page_ms": 8824.7, "create_ms": 246.7, "serve_ms": 631.8, "preview_ms": 299.3, "render_ms": 924.4, "total_ms": 12886.6, "deleted": true}
The outputs this recipe publishes (its recipe.json publish.outputs), with workspace identifiers redacted. The run's stdout, stderr and other outputs are not published.
Captured 2026-11-01 from commit
771a9508
, label
live-recorded
(recorded from production). Case
recipes/sandbox-preview-screenshot
.