An action fetches the data, a sandbox does the analysis, render makes it presentable. The recipe calls io.b3os/open-meteo/get-hourly-forecast for Tokyo, London and New York at once (Open-Meteo is keyless, so the calls name no connection) and writes the three forecasts into a code-interpreter sandbox created with egress: deny: the code that runs there has the data and nothing else to reach.

run_code reads the file into pandas, works out each place's low, high and rain over the 72 hours, and plots the temperatures with matplotlib; the kernel returns the figure as a PNG alongside stdout. render takes an HTML card with the chart inline as a data URI (it draws html with no network), puts it on the ocean background in the laptop-dark device frame and returns the image. The sandbox is deleted before the card is drawn.

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 29fca0d1. 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"post() { curl -s "$API$1" -H "$AUTH" -H "Content-Type: application/json" -d @-; }# 1. actions: 72 hours for each placewhile IFS=, read -r name lat lon; do jq -n --argjson lat "$lat" --argjson lon "$lon" \ '{ref: "io.b3os/open-meteo/get-hourly-forecast@1", input: {latitude: $lat, longitude: $lon, hours: 72}}' \ | post /v1/actions/call | jq --arg place "$name" '{place: $place} + .output.hours'done <<'EOF' | jq -s . > forecast.jsonTokyo,35.68,139.69London,51.51,-0.13New York,40.71,-74.01EOF# 2. sandboxes: a code-interpreter sandbox with no network charts themSBX=$(echo '{"template": "code-interpreter", "timeout_s": 180, "egress": {"mode": "deny"}}' | post /v1/sandboxes | jq -r .id)curl -s -X PUT "$API/v1/sandboxes/$SBX/files?path=/workspace/forecast.json&mkdir=1" -H "$AUTH" \ -H "Content-Type: application/octet-stream" --data-binary @forecast.json > /dev/nullcat > chart.py <<'EOF'import jsonimport matplotlib.pyplot as pltimport pandas as pdrows = json.load(open("/workspace/forecast.json"))fig, ax = plt.subplots(figsize=(11, 4.6), dpi=110)for r in rows: ax.plot(pd.Series(r["temperature"]), label=r["place"], linewidth=2.4)ax.set_xlabel("hours from now")ax.set_ylabel("°C")ax.legend(frameon=False, ncol=3)plt.show()print(json.dumps([{"place": r["place"], "low": min(r["temperature"]), "high": max(r["temperature"])} for r in rows]))EOFjq -n --rawfile code chart.py '{code: $code, language: "python", timeout_ms: 60000}' \| post "/v1/sandboxes/$SBX/run_code" > run.jsoncurl -s -X DELETE "$API/v1/sandboxes/$SBX" -H "$AUTH" > /dev/nulljq -r .stdout run.jsonjq -r '.results[] | select(.mime_type == "image/png") | .data' run.json > chart.b64# 3. render: the chart on a card, on a background in a laptop framejq -n --rawfile png chart.b64 '{ html: ("<body style=\"margin:0;background:#fff;font:20px system-ui;padding:32px\"><h1>The next 72 hours</h1>" + "<img style=\"width:1184px\" src=\"data:image/png;base64," + ($png | rtrimstr("\n")) + "\"></body>"), viewport: {width: 1280, height: 800}, full_page: true, style: {background: "ocean", frame: "laptop-dark"}, format: "webp"}' \| post /v1/render/screenshots | jq -r '.artifacts[0].url' | xargs curl -s -o card.webp

The recipe's run.mjs draws the full card: the three lines in a dark theme and each place's low, high and rain.

The forecasts change every run; the places and the code do not. The card below is the last green run's, drawn in production.

Weather data by Open-Meteo.com, under CC BY 4.0. The card's chart and its lows, highs and rain totals are worked out from that data by the code above; Open-Meteo does not endorse this recipe.

Owner: Docs. Products: Docs, Actions and triggers, 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 Forecasts charted in a sandbox, framed by render

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 29fca0d1 , label live-recorded (recorded from production). Case recipes/forecast-chart-card .

Ask a question... ⌘I