> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beta.b3os.org/llms.txt
> Use this file to discover all available pages before exploring further.

# A routine that scrapes, briefs and screenshots

One routine runs vision/scrape@1, infer/chat@1 and render/screenshot@1 on the node: a one-sentence brief and a styled screenshot, no glue code.

A routine chains product calls on the node itself, so the Markdown from the scrape reaches the model without a round
trip through your code. The screenshot step has `needs: []`, so it starts at the same time as the scrape. Save this
definition as `page-brief.json`:

```json
{
  "name": "page-brief",
  "inputs": { "type": "object", "required": ["url"], "properties": { "url": { "type": "string" } } },
  "steps": [
    { "id": "page", "uses": "vision/scrape@1", "with": { "url": "${{ inputs.url }}", "formats": ["markdown"] } },
    { "id": "brief", "uses": "infer/chat@1", "needs": ["page"],
      "with": { "model": "b3os/qwen3.5-9b", "max_tokens": 120, "chat_template_kwargs": { "enable_thinking": false },
                "messages": [
                  { "role": "system", "content": "In one plain sentence of at most 30 words, say what this web page is for." },
                  { "role": "user", "content": "${{ steps.page.outputs.markdown }}" } ] } },
    { "id": "shot", "uses": "render/screenshot@1", "needs": [],
      "with": { "url": "${{ inputs.url }}", "style": { "background": "b3os-dark", "frame": "browser-dark" }, "format": "webp" } }
  ],
  "output": {
    "brief": "${{ steps.brief.outputs.choices[0].message.content }}",
    "screenshot": "${{ steps.shot.outputs.artifacts[0].url }}"
  }
}
```

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

**curl**

```bash
curl -s -X PUT https://beta-api.b3os.org/v1/routines/page-brief \
  -H "Authorization: Bearer $B3OS_API_KEY" \
  -H "Content-Type: application/json" \
  -d @page-brief.json

curl -s "https://beta-api.b3os.org/v1/routines/page-brief/runs?wait=60" \
  -H "Authorization: Bearer $B3OS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": {"url": "https://beta.b3os.org/demo/billing/"}}' \
  | jq '{status, result, steps: [.steps[] | {id, status, duration_ms}]}'
```
**TypeScript**

```ts
import { readFileSync } from "node:fs";

const API = "https://beta-api.b3os.org";
const headers = { authorization: `Bearer ${process.env.B3OS_API_KEY}`, "content-type": "application/json" };

await fetch(`${API}/v1/routines/page-brief`, {
  method: "PUT",
  headers,
  body: readFileSync("page-brief.json", "utf8"),
});
const run: any = await (
  await fetch(`${API}/v1/routines/page-brief/runs?wait=60`, {
    method: "POST",
    headers,
    body: JSON.stringify({ input: { url: "https://beta.b3os.org/demo/billing/" } }),
  })
).json();
console.log(run.status, run.result.brief);
for (const step of run.steps) console.log(step.id, step.status, step.duration_ms);
```
**Python**

```python
import os
import requests

API = "https://beta-api.b3os.org"
s = requests.Session()
s.headers["Authorization"] = f"Bearer {os.environ['B3OS_API_KEY']}"

with open("page-brief.json") as f:
    s.put(f"{API}/v1/routines/page-brief", data=f.read(), headers={"Content-Type": "application/json"})
run = s.post(
    f"{API}/v1/routines/page-brief/runs",
    params={"wait": 60},
    json={"input": {"url": "https://beta.b3os.org/demo/billing/"}},
).json()
print(run["status"], run["result"]["brief"])
for step in run["steps"]:
    print(step["id"], step["status"], step["duration_ms"])
```
**MCP**

Publish the routine once with the API or the dashboard. Then, with the [B3OS MCP server](/sdks/mcp) connected, ask
your agent:

```text
Run the page-brief routine for https://beta.b3os.org/demo/billing/ and tell me what the page is for.
```

The agent calls `routine_run` with `{"routine": "page-brief", "input": {"url": "https://beta.b3os.org/demo/billing/"}}`.

The recipe's `run.mjs` publishes this definition under a fresh name, with the screenshot's content type and size in
`output` in place of its URL so its checks can read them, runs it once and deletes it. `steps[].duration_ms` is each
step's time on the node.

Owner: Docs. Products: Docs, Routines, Vision, Infer, Render.

## Output of the last green run

**Command**

```bash
$ node run.mjs
# exit 0
```
**Outputs**

```json
{
  "run_status": "succeeded",
  "steps": [
    {
      "id": "page",
      "uses": "vision/scrape@1",
      "status": "succeeded",
      "duration_ms": 0.49
    },
    {
      "id": "brief",
      "uses": "infer/chat@1",
      "status": "succeeded",
      "duration_ms": 116.266
    },
    {
      "id": "shot",
      "uses": "render/screenshot@1",
      "status": "succeeded",
      "duration_ms": 781.697
    }
  ],
  "brief": "This page displays billing plans and invoices for a fictional Example workspace demo.",
  "screenshot_type": "image/webp",
  "screenshot_bytes": 61424,
  "run_ms": 918.3,
  "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/routine-page-brief`.