Asking an AI for a dashboard now takes about four minutes. The result is genuinely nice: cards, trend lines, sensible colors. You share a screenshot and people are impressed.
By Friday it's wrong. Not broken, just wrong. The numbers are Monday's, the dashboard has no way of knowing that, and neither does anyone looking at it. A dashboard is only as good as its data, and the data was frozen into the page the moment it was generated.
This post is about fixing that one problem: keeping the numbers fresh without rebuilding the page.
Why it goes stale
An AI-built dashboard usually bundles three things together:
- The numbers, pasted or computed once.
- The drawing: the HTML, the chart library, the layout.
- The hosting, which usually means a file on your machine or a preview inside the AI tool.
To update the numbers, you regenerate all three. So the dashboard gets updated whenever you remember, which means rarely, and then with a slightly different layout each time.
The fix is to pull them apart. Let something permanent do the drawing and hosting, and automate only the part that changes: the numbers.
The split
On Output Charts, each visual on a dashboard holds one JSON document, and that document is its only data source. The page has a permanent link and redraws whenever a visual's document changes. Pages people already have open check for new data every 15 seconds.
So the AI's job changes. Instead of "build me a dashboard," ask it to:
Write a script that computes these four numbers from [your source] and prints them as JSON in this shape.
That's a much smaller ask, and it's easy to check. You can read a script that sums last week's revenue. Auditing a 900-line HTML file that happens to contain a sum somewhere is another matter.
A worked example
Say the dashboard has a revenue KPI and a weekly revenue area chart, split into new and returning customers. The KPI document:
{
"label": "Revenue this week",
"value": 8420,
"format": "number",
"prefix": "$",
"delta": 0.064,
"deltaFormat": "percent",
"deltaLabel": "vs last week"
}
deltaFormat: "percent" multiplies by 100, so 0.064 shows as 6.4%.
The area chart:
{
"xKey": "week",
"curve": "monotone",
"stacked": true,
"series": [
{ "key": "returning", "label": "Returning customers" },
{ "key": "new", "label": "New customers" }
],
"data": [
{ "week": "Sep 21", "returning": 5100, "new": 2240 },
{ "week": "Sep 28", "returning": 5280, "new": 2010 },
{ "week": "Oct 5", "returning": 5490, "new": 2420 },
{ "week": "Oct 12", "returning": 5710, "new": 2710 }
]
}
Here's a Node script (Node 18 or later, no packages) that pushes both. Where the numbers come from is up to you: a database query, a payments export, a spreadsheet. loadWeeks() stands in for that part. It's what you ask the AI to write.
// push-revenue.mjs
const KEY = process.env.OC_KEY;
async function push(url, document) {
const res = await fetch(url, {
method: "PUT",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(document),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
}
const weeks = await loadWeeks(); // [{ week: "Oct 12", returning: 5710, new: 2710 }, ...]
const total = (w) => w.returning + w.new;
const [prev, last] = weeks.slice(-2);
await push(process.env.OC_KPI_URL, {
label: "Revenue this week",
value: total(last),
format: "number",
prefix: "$",
delta: Number(((total(last) - total(prev)) / total(prev)).toFixed(3)),
deltaFormat: "percent",
deltaLabel: "vs last week",
});
await push(process.env.OC_TREND_URL, {
xKey: "week",
curve: "monotone",
stacked: true,
series: [
{ key: "returning", label: "Returning customers" },
{ key: "new", label: "New customers" },
],
data: weeks.slice(-12),
});
Each visual's push URL comes from Push from code → Copy push URL in the editor. The key comes from Account → API keys and starts with oc_live_. API push is part of Solo ($10 a month).
Then put it on a schedule and forget about it:
0 7 * * 1 cd /home/me/reports && node push-revenue.mjs >> push.log 2>&1
That runs at 7:00 every Monday. If you'd rather not keep a server, the same script runs fine in a scheduled GitHub Actions workflow. Schedule dashboard updates with cron or GitHub Actions walks through both.
Things that go wrong, and what you'll see
- A number arrives as a string. Exports love
"8,420". The API refuses it with a 422 and names the field (data[3].new Expected number), so the chart never quietly shows nothing. - A row has an extra key. Rows must contain exactly the
xKeyand the series keys. Strip extra columns before you push. - The job stops running. The dashboard keeps showing the last good push. Have the job log failures, because
fetchwon't complain on its own unless you checkres.ok, as the script above does.
Honest limits
- The dashboard draws what it's sent and computes nothing, so the script owns the math. That's the point, but it's also where the bugs live.
- Each push replaces the whole document. Send all twelve weeks each time, not just the new one.
- There's no built-in connector to your payment provider or database. If you want point-and-click connectors, a BI tool is the better fit.
- On Free you can still keep it fresh by pasting the new JSON each week. It takes two minutes and doesn't need a key.
Try it
Create a free dashboard, paste the two documents above, and see how they look. Then decide whether Monday-morning-you or a cron job should update them.