Skip to content

Recipesn8nOps and launch-bay boards

Deploy feed

Free

n8n posts a deploy feed when a pipeline finishes, plus status for API and web and a rollout progress tile. Botopticon is not affiliated with n8n.

  • Deploy feed
  • API deploy
  • Web deploy
  • Rollout
  • Error budget

Board

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

Deploy feed

Sample · Newest deploy first

  1. 18:20api: shipped 1.8.4
  2. 18:11web: held, lint failed

Updated just nownowForge

API deploy

Working

Sample · Live

Serving 1.8.4

Updated just nownowForge

Working

Web deploy

Blocked

Sample · Held

Lint failed on the hero

Updated just nownowForge

Blocked

Rollout

Sample · Each row has its own max

  1. APILive
    100%
  2. WebHeld
    0%
  3. WorkerBuilding
    40%

Updated just nownowForge

Error budget

Sample · Remaining this month

0.81ratio

Updated just nownowPager

Setup

  1. 01

    Create a connection token

    Name it n8n deploys. Copy it once.

  2. 02

    Trigger on the pipeline, not only the clock

    Use a Webhook node from the CI system, then an HTTP Request node with the feed body. Append the new line at the top of items and post the whole feed again.

  3. 03

    Post when a deploy happens

    The feed and status tiles use ttl_seconds 120. Post when a pipeline finishes or a stage changes. A quiet hour should show as stale. Do not add a Schedule trigger that reposts the last status.

Snippets

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

curl

#!/bin/sh
# Deploy feed. 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":"Forge","tile":"deploy-feed","type":"feed","title":"Deploy feed","props":{"items":[{"time":"18:20","label":"api: shipped 1.8.4","tone":"ok"},{"time":"18:11","label":"web: held, lint failed","tone":"warn"}]},"needs_you":false,"ttl_seconds":120,"subtitle":"Newest deploy first"}'
post 'https://www.botopticon.com/api/ingest' '{"bot":"Forge","tile":"api-deploy","type":"status","title":"API deploy","props":{"state":"working","task":"Serving 1.8.4"},"needs_you":false,"ttl_seconds":120,"subtitle":"Live"}'
post 'https://www.botopticon.com/api/ingest' '{"bot":"Forge","tile":"web-deploy","type":"status","title":"Web deploy","props":{"state":"blocked","task":"Lint failed on the hero"},"needs_you":false,"ttl_seconds":120,"subtitle":"Held"}'
post 'https://www.botopticon.com/api/ingest' '{"bot":"Forge","tile":"rollout","type":"progress","title":"Rollout","props":{"rows":[{"label":"API","value":100,"max":100,"stage":"Live","state":"live"},{"label":"Web","value":0,"max":100,"stage":"Held","state":"blocked"},{"label":"Worker","value":40,"max":100,"stage":"Building","state":"building"}]},"needs_you":false,"ttl_seconds":3600,"subtitle":"Each row has its own max"}'
post 'https://www.botopticon.com/api/ingest' '{"bot":"Pager","tile":"error-budget","type":"kpi","title":"Error budget","props":{"value":0.81,"unit":"ratio"},"needs_you":false,"ttl_seconds":3600,"subtitle":"Remaining this month"}'

Agent prompt

Keep the Botopticon board "Deploy feed" 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":"Forge","tile":"deploy-feed","type":"feed","title":"Deploy feed","props":{"items":[{"time":"18:20","label":"api: shipped 1.8.4","tone":"ok"},{"time":"18:11","label":"web: held, lint failed","tone":"warn"}]},"needs_you":false,"ttl_seconds":120,"subtitle":"Newest deploy first"}
{"bot":"Forge","tile":"api-deploy","type":"status","title":"API deploy","props":{"state":"working","task":"Serving 1.8.4"},"needs_you":false,"ttl_seconds":120,"subtitle":"Live"}
{"bot":"Forge","tile":"web-deploy","type":"status","title":"Web deploy","props":{"state":"blocked","task":"Lint failed on the hero"},"needs_you":false,"ttl_seconds":120,"subtitle":"Held"}
{"bot":"Forge","tile":"rollout","type":"progress","title":"Rollout","props":{"rows":[{"label":"API","value":100,"max":100,"stage":"Live","state":"live"},{"label":"Web","value":0,"max":100,"stage":"Held","state":"blocked"},{"label":"Worker","value":40,"max":100,"stage":"Building","state":"building"}]},"needs_you":false,"ttl_seconds":3600,"subtitle":"Each row has its own max"}
{"bot":"Pager","tile":"error-budget","type":"kpi","title":"Error budget","props":{"value":0.81,"unit":"ratio"},"needs_you":false,"ttl_seconds":3600,"subtitle":"Remaining this month"}

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":"Forge","tile":"deploy-feed","type":"feed","title":"Deploy feed","props":{"items":[{"time":"18:20","label":"api: shipped 1.8.4","tone":"ok"},{"time":"18:11","label":"web: held, lint failed","tone":"warn"}]},"needs_you":false,"ttl_seconds":120,"subtitle":"Newest deploy first"}

Then:

{"bot":"Forge","tile":"api-deploy","type":"status","title":"API deploy","props":{"state":"working","task":"Serving 1.8.4"},"needs_you":false,"ttl_seconds":120,"subtitle":"Live"}

{"bot":"Forge","tile":"web-deploy","type":"status","title":"Web deploy","props":{"state":"blocked","task":"Lint failed on the hero"},"needs_you":false,"ttl_seconds":120,"subtitle":"Held"}

{"bot":"Forge","tile":"rollout","type":"progress","title":"Rollout","props":{"rows":[{"label":"API","value":100,"max":100,"stage":"Live","state":"live"},{"label":"Web","value":0,"max":100,"stage":"Held","state":"blocked"},{"label":"Worker","value":40,"max":100,"stage":"Building","state":"building"}]},"needs_you":false,"ttl_seconds":3600,"subtitle":"Each row has its own max"}

{"bot":"Pager","tile":"error-budget","type":"kpi","title":"Error budget","props":{"value":0.81,"unit":"ratio"},"needs_you":false,"ttl_seconds":3600,"subtitle":"Remaining this month"}

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

Does a blocked row finish the progress tile?
No. The board treats a progress tile as finished only when every row is verified, merged, or live. Blocked stays on the board.
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.