> ## 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 prediction market's week as an image

Two Polymarket actions read an open bitcoin market and its week of prices, the model writes one sentence, and render draws the card from HTML.

Actions read the data, the model writes the sentence, render draws the picture. The recipe searches Polymarket for
bitcoin markets with `io.b3os/polymarket/search`, keeps the open Yes/No market with the most 24-hour volume that ends
more than a week from now and is not decided, and reads that market's Yes price over the last week, every six hours,
with `io.b3os/polymarket/get-price-history`. Neither action needs a key.

The model gets the numbers already worked out (the price now and a week ago, the change in points, the volume) and
writes one sentence with no advice and no forecast. render takes the card as `html`, draws it with no network on a
background preset and returns the image. Nothing runs between the calls but your code.

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
API=https://beta-api.b3os.org
AUTH="Authorization: Bearer $B3OS_API_KEY"
act() { curl -s "$API/v1/actions/call" -H "$AUTH" -H "Content-Type: application/json" \
  -d "$(jq -n --arg ref "$1" --argjson input "$2" '{ref: $ref, input: $input}')"; }

act io.b3os/polymarket/search@1 '{"query": "bitcoin", "limit": 20}' > found.json
jq '[.output.events[].markets[] | select(.closed == false and .outcomes == ["Yes","No"]
      and .outcome_prices[0] >= 0.05 and .outcome_prices[0] <= 0.95)]
    | sort_by(-(.volume_24h // 0)) | .[0]' found.json > market.json
jq -r .question market.json

act io.b3os/polymarket/get-price-history@1 \
  "$(jq '{token_id: .token_ids[0], interval: "1w", fidelity_minutes: 360}' market.json)" > history.json
jq '.output.history | {points: length, first: .[0].p, last: .[-1].p}' history.json

jq -n --slurpfile m market.json --slurpfile h history.json '{
  model: "b3os/qwen3.5-9b", temperature: 0, max_tokens: 80, chat_template_kwargs: {enable_thinking: false},
  messages: [
    {role: "system", content: "Write one sentence of at most 28 words describing how a prediction market'"'"'s Yes price moved this week. Use only the facts given. No advice, no forecast, no opinion."},
    {role: "user", content: ({question: $m[0].question, yes_price_now: $h[0].output.history[-1].p,
                              yes_price_a_week_ago: $h[0].output.history[0].p} | tojson)}]}' \
| curl -s "$API/v1/infer/chat/completions" -H "$AUTH" -H "Content-Type: application/json" -d @- \
| jq -r '.choices[0].message.content' > brief.txt

jq -n --rawfile b brief.txt --slurpfile m market.json '{
  html: ("<body style=\"margin:0;background:#0d1117;color:#e6edf3;font:24px system-ui;padding:48px\"><h1>"
         + $m[0].question + "</h1><p>" + $b + "</p></body>"),
  viewport: {width: 1120, height: 480}, full_page: true,
  style: {background: "midnight", frame: "none"}, format: "webp"}' \
| curl -s "$API/v1/render/screenshots" -H "$AUTH" -H "Content-Type: application/json" -d @- \
| jq -r '.artifacts[0].url' | xargs curl -s -o card.webp
```

The recipe's `run.mjs` draws the full card: the price, the change and the week as an SVG line.
**TypeScript**

```ts
const API = "https://beta-api.b3os.org";
const headers = { authorization: `Bearer ${process.env.B3OS_API_KEY}`, "content-type": "application/json" };
const post = async (path: string, body: unknown): Promise<any> =>
  (await fetch(`${API}${path}`, { method: "POST", headers, body: JSON.stringify(body) })).json();
const act = (ref: string, input: unknown) => post("/v1/actions/call", { ref, input });

const found = await act("io.b3os/polymarket/search@1", { query: "bitcoin", limit: 20 });
const market = found.output.events
  .flatMap((e: any) => e.markets)
  .filter((m: any) => !m.closed && m.outcomes.join() === "Yes,No" && m.outcome_prices[0] >= 0.05 && m.outcome_prices[0] <= 0.95)
  .sort((a: any, b: any) => (b.volume_24h ?? 0) - (a.volume_24h ?? 0))[0];
const { output } = await act("io.b3os/polymarket/get-price-history@1", {
  token_id: market.token_ids[0],
  interval: "1w",
  fidelity_minutes: 360,
});
const now = output.history.at(-1).p;
const before = output.history[0].p;

const chat = await post("/v1/infer/chat/completions", {
  model: "b3os/qwen3.5-9b",
  temperature: 0,
  max_tokens: 80,
  chat_template_kwargs: { enable_thinking: false },
  messages: [
    { role: "system", content: "Write one sentence of at most 28 words describing how a prediction market's Yes price moved this week. Use only the facts given. No advice, no forecast, no opinion." },
    { role: "user", content: JSON.stringify({ question: market.question, yes_price_now: now, yes_price_a_week_ago: before }) },
  ],
});
const brief = chat.choices[0].message.content;

const shot = await post("/v1/render/screenshots", {
  html: `<body style="margin:0;background:#0d1117;color:#e6edf3;font:24px system-ui;padding:48px"><h1>${market.question}</h1><p>${brief}</p></body>`,
  viewport: { width: 1120, height: 480 },
  full_page: true,
  style: { background: "midnight", frame: "none" },
  format: "webp",
});
console.log(shot.artifacts[0].url);
```
**Python**

```python
import json
import os
import requests

API = "https://beta-api.b3os.org"
s = requests.Session()
s.headers["Authorization"] = f"Bearer {os.environ['B3OS_API_KEY']}"
act = lambda ref, inp: s.post(f"{API}/v1/actions/call", json={"ref": ref, "input": inp}).json()

found = act("io.b3os/polymarket/search@1", {"query": "bitcoin", "limit": 20})
markets = [m for e in found["output"]["events"] for m in e["markets"]
           if not m["closed"] and m["outcomes"] == ["Yes", "No"] and 0.05 <= m["outcome_prices"][0] <= 0.95]
market = max(markets, key=lambda m: m.get("volume_24h") or 0)
history = act("io.b3os/polymarket/get-price-history@1",
              {"token_id": market["token_ids"][0], "interval": "1w", "fidelity_minutes": 360})["output"]["history"]

chat = s.post(f"{API}/v1/infer/chat/completions", json={
    "model": "b3os/qwen3.5-9b", "temperature": 0, "max_tokens": 80,
    "chat_template_kwargs": {"enable_thinking": False},
    "messages": [
        {"role": "system", "content": "Write one sentence of at most 28 words describing how a prediction market's "
                                      "Yes price moved this week. Use only the facts given. No advice, no forecast, no opinion."},
        {"role": "user", "content": json.dumps({"question": market["question"], "yes_price_now": history[-1]["p"],
                                                "yes_price_a_week_ago": history[0]["p"]})},
    ],
}).json()
brief = chat["choices"][0]["message"]["content"]

shot = s.post(f"{API}/v1/render/screenshots", json={
    "html": f'<body style="margin:0;background:#0d1117;color:#e6edf3;font:24px system-ui;padding:48px">'
            f'<h1>{market["question"]}</h1><p>{brief}</p></body>',
    "viewport": {"width": 1120, "height": 480}, "full_page": True,
    "style": {"background": "midnight", "frame": "none"}, "format": "webp",
}).json()
print(shot["artifacts"][0]["url"])
```
**MCP**

On the remote [B3OS MCP server](/sdks/mcp), ask your agent:

```text
Search Polymarket for bitcoin markets with the polymarket search action. Take the open Yes/No market with the most
24-hour volume whose Yes price is between 5 and 95 cents, and get its Yes price history for the last week every 6
hours. Write one sentence on how the price moved, with no advice and no forecast. Then render a 1120 px wide card from
HTML with the question and that sentence on the midnight background, and give me the image URL.
```

The agent calls `actions_call` twice, writes the sentence itself and calls `render_screenshot` with `html`.

The market changes every run: the card below is the last green run's, drawn in production. A question's text is
Polymarket's; escape it before it goes into HTML, as `run.mjs` does.

Owner: Docs. Products: Docs, Actions and triggers, Infer, Render.

## Output of the last green run

**Command**

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

```json
{
  "question": "Will Bitcoin reach $110,000 by December 31, 2026?",
  "market_url": "https://polymarket.com/event/what-price-will-bitcoin-hit-before-2027",
  "end_date": "2027-01-01T05:00:00Z",
  "volume_24h_usd": 33052,
  "markets_seen": 227,
  "points": 23,
  "yes_now_cents": 6.5,
  "yes_week_ago_cents": 7.5,
  "change_points": -1,
  "model": "b3os/qwen3.5-9b",
  "brief": "The prediction market's Yes price for Bitcoin reaching $110,000 by December 31, 2026, moved down 1 point to 6.5 cents this week.",
  "render_status": "succeeded",
  "card": "https://beta.b3os.org/gallery/recipes/market-brief-card/b8cc354061d2d5f0.webp",
  "card_bytes": 56898,
  "search_ms": 868.2,
  "history_ms": 454,
  "brief_ms": 277.6,
  "render_ms": 105.9,
  "total_ms": 2051.4
}
```

![Image 1 of 1 from the last green run of the recipe A prediction market's week as an image](https://beta.b3os.org/gallery/recipes/market-brief-card/b8cc354061d2d5f0.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 `771a9508`, label `live-recorded` (recorded from production). Case `recipes/market-brief-card`.