A live ops dashboard that updates without polling

One aggregator publishes to one channel and every viewer reads the same stream. Why fan-in beats fan-out here, and how to stop working when nobody is watching.

An ops dashboard that polls every five seconds is twelve requests a minute per viewer, most of which return the same numbers. On Infrai the shape that works is inverted: one aggregator process computes the view, publishes it with POST /v1/realtime/publish, and every browser subscribes to the same channel. Twenty viewers cost exactly as much as one.

That asymmetry is the argument. One publish, any number of readers.

One channel, one publisher

curl -sS -X POST "https://api.infrai.cc/v1/realtime/channel/create" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"channel": "ops:overview", "type": "private"}'
{
  "ok": true,
  "data": {
    "channel_id": "chn_80859a23cec1419f9743d7",
    "name": "ops:overview",
    "type": "private",
    "vendor": "tencent_im",
    "created_at": "2026-09-21T03:12:00Z",
    "member_count": 0,
    "last_published_at": null
  }
}

private rather than public, because an ops dashboard carries numbers you wouldn’t post publicly, and private channels require a capability token to subscribe. Viewers get subscribe only — nobody but the aggregator publishes.

The aggregator

import os
import time

import requests

API = "https://api.infrai.cc"
KEY = os.environ["INFRAI_API_KEY"]
CHANNEL = "ops:overview"
SESSION = requests.Session()
SESSION.headers.update({"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})


def snapshot() -> dict:
    """Build the whole dashboard payload from reads on the same key. Spend,
    queue depth and error volume are the three panels people actually watch."""
    usage = SESSION.get(f"{API}/v1/account/usage", timeout=20).json()["data"]
    balance = SESSION.get(f"{API}/v1/account/balance", timeout=20).json()["data"]
    queues = SESSION.get(f"{API}/v1/queue/list", timeout=20).json()["data"]

    return {
        "type": "ops.snapshot",
        "spend_period_usd": round(usage.get("total_cost") or 0, 2),
        "calls": usage.get("total_calls"),
        "failed_calls": usage.get("total_failed_calls"),
        "runway_days": balance.get("runway_days"),
        "queues": [q.get("name") for q in (queues.get("items") or [])][:10],
        "at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
    }


def publish(payload: dict) -> str | None:
    resp = SESSION.post(
        f"{API}/v1/realtime/publish",
        json={"channel": CHANNEL, "event": "message.published", "data": payload},
        timeout=10,
    )
    body = resp.json()
    return body["data"]["event_id"] if body.get("ok") else None


def viewers() -> int:
    resp = SESSION.get(f"{API}/v1/realtime/presence/get/{CHANNEL}", timeout=15)
    if not resp.ok:
        return 0
    return len(resp.json()["data"].get("members") or [])


if __name__ == "__main__":
    while True:
        # Don't compute a dashboard nobody is looking at. A presence read is far
        # cheaper than the three aggregate reads plus a publish.
        if viewers() == 0:
            time.sleep(30)
            continue
        publish(snapshot())
        time.sleep(5)

The viewers() check is the detail that makes this cheap to run overnight. A dashboard with nobody watching is a loop burning API calls to produce numbers that go nowhere.

Note that a presence read needs the channel to be type: "presence" rather than private if you want the roster — pick one shape deliberately: presence costs you the roster tracking and gives you the idle detection above.

Or let the platform tell you when to start

If you’d rather not poll presence at all, subscribe to the channel lifecycle:

curl -sS -X POST "https://api.infrai.cc/v1/account/webhooks/register" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://ops.example.com/hooks/dashboard",
    "events": ["realtime.channel.occupied", "realtime.channel.vacated"],
    "description": "start and stop the ops aggregator",
    "secret": "a-long-random-string-you-generate"
  }'

realtime.channel.occupied fires when the first viewer arrives and realtime.channel.vacated when the last leaves. Start the aggregator on the first and stop it on the second, and the steady-state cost of the dashboard while nobody watches is zero.

Keep the numbers, not just the pixels

A live dashboard shows now. The question after the incident is always “what did it look like at 03:40”, and a fan-out event answers nothing retrospectively.

So write the same snapshot into POST /v1/metrics/report as you publish it, and query it back with GET /v1/metrics/query. The realtime channel is the live view; the metrics series is the record. One key covers both, which means you’re not choosing between a messaging vendor and a monitoring vendor — you’re making two calls in the same function.

PanelLive sourceHistorical source
Spend this periodGET /v1/account/usagePOST /v1/metrics/report series
Runway in daysGET /v1/account/balancesame
Queue depthGET /v1/queue/stats/{queue}same
Error volumeGET /v1/errors/groupserror groups persist already
Who’s watchingGET /v1/realtime/presence/get/{channel}not retained

Limitations

There’s no history on the channel, so a browser that reconnects sees nothing until the next publish — which for a five-second interval is invisible, and for a one-minute interval is a blank dashboard for up to a minute. Fetch the current snapshot over plain HTTP on load, then let the stream take over; that’s two code paths, and skipping the first is the most common reason a dashboard “doesn’t work” after a laptop wakes up.

The fan-out vendor today is tencent_im, with Ably and Pusher listed as pending in GET /v1/discovery/realtime.publish, so check readiness rather than assuming. And if you want a dashboard product rather than a dashboard you built — alert rules, annotations, team sharing — Grafana with a data source is a much better use of your afternoon than assembling one from these calls.

Publishing is billed per call at a rate live in GET /v1/discovery/realtime.publish (verified 2026-09-21); channel, presence and token calls report billing_class: free. At a five-second interval the whole dashboard is a rounding error on the spend it displays, and platform rates drift downward as vendor contracts improve.

References

Browse more realtime developer guides