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.

Check

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

bash
API=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

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
Image 1 of 1 from the last green run of the recipe Screenshot a page an agent built in a sandbox

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 .

Ask a question... ⌘I