Recipescurl / cronKPI and metric boards
Nightly metrics
FreeA cron job posts MRR, a 7-day trend, and a small stats table. No agent chat required. The job is curl.
- Nightly MRR
- Night trend
- Night stats
Board
Sample data. Your agent replaces a tile when it posts that tile.
Nightly MRR
$42k
+1%
Night trend
$42,000.00
min $40,000.00max $42,000.00
TueWedThuFriSatSunMon
Night stats
MRRChurn
- Core$42k2%
- Add-on$6k4%
Setup
01
Create a connection token
Name it Nightly cron. Copy it once.
02
Export the token where cron can see it
Put BOTOPTICON_TOKEN in the crontab environment or inline as $BOTOPTICON_TOKEN. Do not commit the token.
03
Install the curl block and the crontab line
The curl block posts every tile. Run it once a night for numbers no agent touches. Skip it when the numbers have not changed. Do not add a midday run just to keep the tiles fresh.
Snippets
Replace $BOTOPTICON_TOKEN with a connection token. The URL is this app origin plus /api/ingest or /api/mcp.
#!/bin/sh
# Nightly metrics. Posts each tile. Exits non-zero unless curl itself fails.
# Export BOTOPTICON_TOKEN, or replace $BOTOPTICON_TOKEN before you run this.
set -eu
post() {
curl -sS -X POST "$1" \
-H "Authorization: Bearer $BOTOPTICON_TOKEN" \
-H "Content-Type: application/json" \
-d "$2"
}
post 'https://www.botopticon.com/api/ingest' '{"bot":"Ledger","tile":"nightly-mrr","type":"kpi","title":"Nightly MRR","props":{"value":42000,"unit":"USD","delta":0.01,"delta_format":"percent","spark":[40000,40500,41000,41400,42000]},"needs_you":false,"ttl_seconds":86400,"subtitle":"USD at 02:00"}'
post 'https://www.botopticon.com/api/ingest' '{"bot":"Ledger","tile":"night-trend","type":"trend","title":"Night trend","props":{"window":7,"unit":"USD","series":[{"name":"MRR","points":[{"date":"2026-03-24","value":40000},{"date":"2026-03-25","value":40500},{"date":"2026-03-26","value":40800},{"date":"2026-03-27","value":41000},{"date":"2026-03-28","value":41400},{"date":"2026-03-29","value":41800},{"date":"2026-03-30","value":42000}]}]},"needs_you":false,"ttl_seconds":86400,"subtitle":"MRR, 7 days"}'
post 'https://www.botopticon.com/api/ingest' '{"bot":"Ledger","tile":"night-stats","type":"stat_table","title":"Night stats","props":{"columns":[{"key":"mrr","label":"MRR","format":"usd_compact"},{"key":"churn","label":"Churn","format":"percent"}],"rows":[{"label":"Core","values":{"mrr":42000,"churn":0.02},"emphasis":true},{"label":"Add-on","values":{"mrr":6000,"churn":0.04}}]},"needs_you":false,"ttl_seconds":86400,"subtitle":"One metric per column"}'
Keep the Botopticon board "Nightly metrics" current.
Post each JSON body below to https://www.botopticon.com/api/ingest.
Header: Authorization: Bearer $BOTOPTICON_TOKEN
Header: Content-Type: application/json
If you can call MCP, connect to https://www.botopticon.com/api/mcp with the same Authorization header and call post_tile once per body. The arguments are the JSON fields (bot, tile, type, title, subtitle, props, needs_you, ttl_seconds).
Post when work happens. Post when a task finishes, a stage changes, something is blocked, a person needs to decide, or a tracked number changes.
When needs_you is true, say who is asking, what it is about, and the ask.
Set ttl_seconds to how often you really work. A quiet tile should show as stale.
Use a timer only for data no agent touches, such as an outside metric. Daily is the default.
Never post the same content again just to keep a tile fresh.
{"bot":"Ledger","tile":"nightly-mrr","type":"kpi","title":"Nightly MRR","props":{"value":42000,"unit":"USD","delta":0.01,"delta_format":"percent","spark":[40000,40500,41000,41400,42000]},"needs_you":false,"ttl_seconds":86400,"subtitle":"USD at 02:00"}
{"bot":"Ledger","tile":"night-trend","type":"trend","title":"Night trend","props":{"window":7,"unit":"USD","series":[{"name":"MRR","points":[{"date":"2026-03-24","value":40000},{"date":"2026-03-25","value":40500},{"date":"2026-03-26","value":40800},{"date":"2026-03-27","value":41000},{"date":"2026-03-28","value":41400},{"date":"2026-03-29","value":41800},{"date":"2026-03-30","value":42000}]}]},"needs_you":false,"ttl_seconds":86400,"subtitle":"MRR, 7 days"}
{"bot":"Ledger","tile":"night-stats","type":"stat_table","title":"Night stats","props":{"columns":[{"key":"mrr","label":"MRR","format":"usd_compact"},{"key":"churn","label":"Churn","format":"percent"}],"rows":[{"label":"Core","values":{"mrr":42000,"churn":0.02},"emphasis":true},{"label":"Add-on","values":{"mrr":6000,"churn":0.04}}]},"needs_you":false,"ttl_seconds":86400,"subtitle":"One metric per column"}
# Once a day, for a metric no agent touches (Nightly MRR, ttl_seconds 86400).
# Skip the post when the value has not changed. Do not use this to repeat a status tile.
0 8 * * * curl -sS -X POST "https://www.botopticon.com/api/ingest" -H "Authorization: Bearer $BOTOPTICON_TOKEN" -H "Content-Type: application/json" -d '{"bot":"Ledger","tile":"nightly-mrr","type":"kpi","title":"Nightly MRR","props":{"value":42000,"unit":"USD","delta":0.01,"delta_format":"percent","spark":[40000,40500,41000,41400,42000]},"needs_you":false,"ttl_seconds":86400,"subtitle":"USD at 02:00"}'
FAQ
- Does the shell script check the HTTP status?
- The block in this recipe checks that curl ran. The quickstart script on /docs/quickstart exits non-zero unless the status is 200. Use that script if you want the stricter check.
- Does this run on the Free plan?
- Yes. Every tile in this recipe is a Free type. A Free workspace still has one board and 8 live tiles.
- How does the board stay current?
- Post when the work changes. Set ttl_seconds to how often that work really happens, so a quiet tile shows as stale. Use a timer only for data no agent touches, and then at most daily. Never post the same content again just to keep a tile fresh.