> ## 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.

# Approve, then screenshot

A routine scrapes a page, waits for a person's approval as a signal, and only then takes a screenshot of it.

A `wait` step holds a run until someone answers it: here a routine reads a page with `vision/scrape@1`, waits for
the `approve` signal, and takes a screenshot with `render/screenshot@1` only when the payload says
`approved: true`. The run costs nothing while it waits, and gives up after the step's `ms` (10 minutes here). The
recipe publishes the routine, starts a run, sends the approval once the run waits for it, and deletes the routine.
Owner: routines.

Not verified yet: no docs-examples run has covered the runnable snippets on this page.

## The routine

Save it as `routine.json`:

```json
{
  "name": "recipe-approve-then-screenshot",
  "inputs": { "type": "object", "properties": { "url": { "type": "string", "default": "https://example.com" } } },
  "steps": [
    { "id": "page", "uses": "vision/scrape@1", "with": { "url": "${{ inputs.url }}", "formats": ["markdown"] } },
    { "id": "approval", "wait": { "signal": "approve", "ms": 600000 } },
    {
      "id": "shot",
      "if": "${{ steps.approval.outputs.payload.approved == true }}",
      "uses": "render/screenshot@1",
      "with": { "url": "${{ inputs.url }}" }
    }
  ],
  "output": {
    "summary": "Approved by ${{ steps.approval.outputs.payload.by }}; screenshot taken",
    "screenshot": "${{ steps.shot.outputs.artifacts[0].url }}"
  }
}
```

## Run it

Publish it, start a run, and approve it once it waits:

**curl**

```bash
curl -s -X PUT https://beta-api.b3os.org/v1/routines/recipe-approve-then-screenshot \
  -H "Authorization: Bearer $B3OS_API_KEY" -H "Content-Type: application/json" -d @routine.json
RUN_ID=$(curl -s https://beta-api.b3os.org/v1/routines/recipe-approve-then-screenshot/runs \
  -H "Authorization: Bearer $B3OS_API_KEY" -H "Content-Type: application/json" -d '{}' | jq -r .id)
curl -s "https://beta-api.b3os.org/v1/routines/runs/$RUN_ID/signal" \
  -H "Authorization: Bearer $B3OS_API_KEY" -H "Content-Type: application/json" \
  -d '{"name": "approve", "payload": {"approved": true, "by": "ops"}}'
```
**TypeScript**

```ts
import { readFileSync } from "node:fs";
import { B3OS } from "@b3os/sdk";

const b3os = new B3OS(); // reads B3OS_API_KEY
const def = JSON.parse(readFileSync("routine.json", "utf8"));
await b3os.routines.publishRoutine(def.name, def);
const run = await b3os.routines.createRun(def.name, {});
await b3os.routines.signalRun(run.id, { name: "approve", payload: { approved: true, by: "ops" } });
const done = await b3os.routines.getRun(run.id);
console.log(done.status, done.result);
```
**Python**

```python
import json
from b3os import B3OS

b3os = B3OS()  # reads B3OS_API_KEY
definition = json.load(open("routine.json"))
b3os.routines.publish_routine(definition["name"], definition)
run = b3os.routines.create_run(definition["name"], {})
b3os.routines.signal_run(run["id"], {"name": "approve", "payload": {"approved": True, "by": "ops"}})
print(b3os.routines.get_run(run["id"])["status"])
```
**CLI**

```bash
b3os routine deploy routine.json
b3os routine run recipe-approve-then-screenshot --no-wait
b3os routine signal "$RUN_ID" approve --payload '{"approved": true, "by": "ops"}'
```

## What it produced

**Command**

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

```json
{
  "run_id": "run_01m6awj44cqa6qx6h9v6d0crs2",
  "run_status": "succeeded",
  "waited_for_approval": true,
  "signal_to_finish_ms": 172,
  "steps": [
    {
      "id": "page",
      "uses": "vision/scrape@1",
      "status": "succeeded",
      "duration_ms": 0.25
    },
    {
      "id": "approval",
      "uses": "core/wait@1",
      "status": "succeeded",
      "duration_ms": 564.373
    },
    {
      "id": "shot",
      "uses": "render/screenshot@1",
      "status": "succeeded",
      "duration_ms": 148.029
    }
  ],
  "markdown_chars": 156,
  "screenshot_taken": true,
  "screenshot_format": "webp"
}
```

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 an unknown commit, label `live-recorded` (recorded from production). Case `recipes/approve-then-screenshot`.

## How it works

A signal sent before the step waits is buffered, so the approval can come at any time after the run starts. The
waiting step completes with the payload as its outputs, and the `shot` step's `if` reads it: a payload without
`approved: true` skips the screenshot. In the dashboard, the run page shows the waiting step with a payload box
that sends the signal. A signal to a run that already finished answers `409 run_not_waiting`.

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

## Output of the last green run

**Command**

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

```json
{
  "run_id": "run_01m6awj44cqa6qx6h9v6d0crs2",
  "run_status": "succeeded",
  "waited_for_approval": true,
  "signal_to_finish_ms": 172,
  "steps": [
    {
      "id": "page",
      "uses": "vision/scrape@1",
      "status": "succeeded",
      "duration_ms": 0.25
    },
    {
      "id": "approval",
      "uses": "core/wait@1",
      "status": "succeeded",
      "duration_ms": 564.373
    },
    {
      "id": "shot",
      "uses": "render/screenshot@1",
      "status": "succeeded",
      "duration_ms": 148.029
    }
  ],
  "markdown_chars": 156,
  "screenshot_taken": true,
  "screenshot_format": "webp"
}
```

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 an unknown commit, label `live-recorded` (recorded from production). Case `recipes/approve-then-screenshot`.