Skip to content

Recipesn8nKPI and metric boards

Morning numbers

Free

An n8n schedule posts MRR, signups, open tickets, a 7-day trend, and a channel table each morning. Botopticon is not affiliated with n8n.

  • Morning MRR
  • New signups
  • Tickets open
  • Week trend
  • Channel stats

Board

Sample data. Your agent replaces a tile when it posts that tile.

Morning MRR

Sample · USD, posted at 7am

$42k

+1%

Updated just nownowLedger

New signups

Sample · Since yesterday

24accounts

+3

Updated just nownowLedger

Tickets open

Sample · Still open

17tickets

-4

Updated just nownowPager

Week trend

Sample · Signups, 7 days

24

min 18max 30

TueWedThuFriSatSunMon

Updated just nownowLedger

Channel stats

Sample · One metric per column

MRRSignups
  1. Site$28k16
  2. Partners$14k8

Updated just nownowLedger

Setup

  1. 01

    Create a connection token

    Name it n8n morning. Copy it once.

  2. 02

    Add an HTTP Request node

    Method POST. URL is your origin plus /api/ingest. Header Authorization: Bearer and the token. Header Content-Type: application/json. Body is one JSON object from the n8n snippet. Duplicate the node once per tile, or loop the bodies.

  3. 03

    Trigger it every morning

    Use a Schedule trigger once a day. These tiles are numbers no agent touches, and ttl_seconds is 86400. Skip the run when the numbers have not changed. Do not add a midday run just to keep them fresh.

Snippets

Replace $BOTOPTICON_TOKEN with a connection token. The URL is this app origin plus /api/ingest or /api/mcp.

curl

#!/bin/sh
# Morning numbers. 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":"morning-mrr","type":"kpi","title":"Morning MRR","props":{"value":42000,"unit":"USD","delta":0.01,"delta_format":"percent"},"needs_you":false,"ttl_seconds":86400,"subtitle":"USD, posted at 7am"}'
post 'https://www.botopticon.com/api/ingest' '{"bot":"Ledger","tile":"new-signups","type":"kpi","title":"New signups","props":{"value":24,"unit":"accounts","delta":3,"delta_format":"absolute"},"needs_you":false,"ttl_seconds":86400,"subtitle":"Since yesterday"}'
post 'https://www.botopticon.com/api/ingest' '{"bot":"Pager","tile":"tickets-open","type":"kpi","title":"Tickets open","props":{"value":17,"unit":"tickets","delta":-4,"delta_format":"absolute"},"needs_you":false,"ttl_seconds":86400,"subtitle":"Still open"}'
post 'https://www.botopticon.com/api/ingest' '{"bot":"Ledger","tile":"week-trend","type":"trend","title":"Week trend","props":{"window":7,"unit":"accounts","series":[{"name":"signups","points":[{"date":"2026-03-24","value":18},{"date":"2026-03-25","value":21},{"date":"2026-03-26","value":19},{"date":"2026-03-27","value":30},{"date":"2026-03-28","value":22},{"date":"2026-03-29","value":25},{"date":"2026-03-30","value":24}]}]},"needs_you":false,"ttl_seconds":86400,"subtitle":"Signups, 7 days"}'
post 'https://www.botopticon.com/api/ingest' '{"bot":"Ledger","tile":"channel-stats","type":"stat_table","title":"Channel stats","props":{"columns":[{"key":"mrr","label":"MRR","format":"usd_compact"},{"key":"signups","label":"Signups","format":"number"}],"rows":[{"label":"Site","values":{"mrr":28000,"signups":16}},{"label":"Partners","values":{"mrr":14000,"signups":8},"emphasis":true}]},"needs_you":false,"ttl_seconds":86400,"subtitle":"One metric per column"}'

Agent prompt

Keep the Botopticon board "Morning numbers" 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":"morning-mrr","type":"kpi","title":"Morning MRR","props":{"value":42000,"unit":"USD","delta":0.01,"delta_format":"percent"},"needs_you":false,"ttl_seconds":86400,"subtitle":"USD, posted at 7am"}
{"bot":"Ledger","tile":"new-signups","type":"kpi","title":"New signups","props":{"value":24,"unit":"accounts","delta":3,"delta_format":"absolute"},"needs_you":false,"ttl_seconds":86400,"subtitle":"Since yesterday"}
{"bot":"Pager","tile":"tickets-open","type":"kpi","title":"Tickets open","props":{"value":17,"unit":"tickets","delta":-4,"delta_format":"absolute"},"needs_you":false,"ttl_seconds":86400,"subtitle":"Still open"}
{"bot":"Ledger","tile":"week-trend","type":"trend","title":"Week trend","props":{"window":7,"unit":"accounts","series":[{"name":"signups","points":[{"date":"2026-03-24","value":18},{"date":"2026-03-25","value":21},{"date":"2026-03-26","value":19},{"date":"2026-03-27","value":30},{"date":"2026-03-28","value":22},{"date":"2026-03-29","value":25},{"date":"2026-03-30","value":24}]}]},"needs_you":false,"ttl_seconds":86400,"subtitle":"Signups, 7 days"}
{"bot":"Ledger","tile":"channel-stats","type":"stat_table","title":"Channel stats","props":{"columns":[{"key":"mrr","label":"MRR","format":"usd_compact"},{"key":"signups","label":"Signups","format":"number"}],"rows":[{"label":"Site","values":{"mrr":28000,"signups":16}},{"label":"Partners","values":{"mrr":14000,"signups":8},"emphasis":true}]},"needs_you":false,"ttl_seconds":86400,"subtitle":"One metric per column"}

n8n HTTP Request

n8n HTTP Request node.
Method: POST
URL: https://www.botopticon.com/api/ingest
Header: Authorization: Bearer $BOTOPTICON_TOKEN
Header: Content-Type: application/json

Send one request per tile. First body:

{"bot":"Ledger","tile":"morning-mrr","type":"kpi","title":"Morning MRR","props":{"value":42000,"unit":"USD","delta":0.01,"delta_format":"percent"},"needs_you":false,"ttl_seconds":86400,"subtitle":"USD, posted at 7am"}

Then:

{"bot":"Ledger","tile":"new-signups","type":"kpi","title":"New signups","props":{"value":24,"unit":"accounts","delta":3,"delta_format":"absolute"},"needs_you":false,"ttl_seconds":86400,"subtitle":"Since yesterday"}

{"bot":"Pager","tile":"tickets-open","type":"kpi","title":"Tickets open","props":{"value":17,"unit":"tickets","delta":-4,"delta_format":"absolute"},"needs_you":false,"ttl_seconds":86400,"subtitle":"Still open"}

{"bot":"Ledger","tile":"week-trend","type":"trend","title":"Week trend","props":{"window":7,"unit":"accounts","series":[{"name":"signups","points":[{"date":"2026-03-24","value":18},{"date":"2026-03-25","value":21},{"date":"2026-03-26","value":19},{"date":"2026-03-27","value":30},{"date":"2026-03-28","value":22},{"date":"2026-03-29","value":25},{"date":"2026-03-30","value":24}]}]},"needs_you":false,"ttl_seconds":86400,"subtitle":"Signups, 7 days"}

{"bot":"Ledger","tile":"channel-stats","type":"stat_table","title":"Channel stats","props":{"columns":[{"key":"mrr","label":"MRR","format":"usd_compact"},{"key":"signups","label":"Signups","format":"number"}],"rows":[{"label":"Site","values":{"mrr":28000,"signups":16}},{"label":"Partners","values":{"mrr":14000,"signups":8},"emphasis":true}]},"needs_you":false,"ttl_seconds":86400,"subtitle":"One metric per column"}

Post when the number changes. Use a timer only for data no agent touches, and then at most once a day. Never post the same body again just to keep a tile fresh.

FAQ

One node or one per tile?
One POST per tile. Same URL and headers. The body changes. n8n can loop an array of the JSON bodies in the snippet.
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.