Recipe: a live dashboard
Publish a dashboard once, then push new numbers to it on a schedule. People who have it open see the numbers change without reloading.
You need a DocPin workspace and either an API key (Connections → API keys) or DocPin connected to Claude or ChatGPT.
1. The page
A complete page. It reads everything from page data, handles missing values, and re-checks every minute.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sales dashboard</title>
<style>
body { font: 16px/1.5 system-ui, sans-serif; margin: 0; padding: 32px; background: #f7f6f3; color: #1d1c1a }
h1 { margin: 0 0 4px; font-size: 28px }
.asof { color: #6b6762; margin: 0 0 24px }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px }
.card { background: #fff; border: 1px solid #e8e5df; border-radius: 14px; padding: 18px }
.label { color: #6b6762; font-size: 14px }
.value { font-size: 36px; font-weight: 700; margin-top: 4px }
table { width: 100%; border-collapse: collapse; margin-top: 24px; background: #fff; border-radius: 14px; overflow: hidden }
td, th { text-align: left; padding: 10px 14px; border-top: 1px solid #e8e5df }
th { background: #f3f1ec; border-top: 0 }
.empty { color: #6b6762 }
</style>
</head>
<body>
<h1>Sales dashboard</h1>
<p class="asof" id="asof">Loading…</p>
<div class="cards">
<div class="card"><div class="label">Revenue this month</div><div class="value" id="revenue">–</div></div>
<div class="card"><div class="label">New customers</div><div class="value" id="customers">–</div></div>
</div>
<table>
<thead><tr><th>Region</th><th>Revenue</th></tr></thead>
<tbody id="regions"><tr><td class="empty" colspan="2">No regions yet</td></tr></tbody>
</table>
<script>
const money = (n) => typeof n === "number" ? "$" + n.toLocaleString() : "–";
const text = (id, value) => { document.getElementById(id).textContent = value; };
function render(data) {
text("revenue", money(data.revenue));
text("customers", typeof data.customers === "number" ? data.customers.toLocaleString() : "–");
text("asof", data.asOf ? "As of " + new Date(data.asOf).toLocaleString() : "No data yet");
const rows = Array.isArray(data.regions) ? data.regions : [];
const body = document.getElementById("regions");
body.replaceChildren(...(rows.length ? rows : [null]).map((r) => {
const tr = document.createElement("tr");
if (!r) { tr.innerHTML = '<td class="empty" colspan="2">No regions yet</td>'; return tr; }
const name = document.createElement("td"); name.textContent = String(r.name ?? "");
const value = document.createElement("td"); value.textContent = money(r.revenue);
tr.append(name, value);
return tr;
}));
}
if (window.docpin) docpin.onData(render, { every: 60 });
else render({});
</script>
</body>
</html>It uses textContent, not innerHTML, for data values, so text in the data can't inject markup.
2. Publish it
From the API:
curl https://api.docpin.app/v1/pages \
-H "Authorization: Bearer $DOCPIN_API_KEY" \
-F file=@dashboard.html -F slug=salesThe response includes url (the link to share) and doc.id. Save doc.id as PAGE_ID.
From Claude or ChatGPT: "Publish this to DocPin as sales."
3. Push numbers
From the API, from cron, CI or any scheduler:
curl -X PATCH https://api.docpin.app/v1/pages/$PAGE_ID/data \
-H "Authorization: Bearer $DOCPIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"revenue": 128400,
"customers": 37,
"regions": [{"name": "New Zealand", "revenue": 51200}, {"name": "Australia", "revenue": 77200}],
"asOf": "2026-11-06T09:00:00Z"
}'From Node.js:
const res = await fetch(`https://api.docpin.app/v1/pages/${process.env.PAGE_ID}/data`, {
method: "PATCH",
headers: { Authorization: `Bearer ${process.env.DOCPIN_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ revenue: 128400, customers: 37, asOf: new Date().toISOString() }),
});
if (!res.ok) throw new Error((await res.json()).error);From Python:
import os, datetime, requests
r = requests.patch(
f"https://api.docpin.app/v1/pages/{os.environ['PAGE_ID']}/data",
headers={"Authorization": f"Bearer {os.environ['DOCPIN_API_KEY']}"},
json={"revenue": 128400, "customers": 37, "asOf": datetime.datetime.now(datetime.timezone.utc).isoformat()},
)
r.raise_for_status()From Claude or ChatGPT: "Update the data on the sales dashboard: revenue 128,400, 37 customers." The app calls set_page_data.
4. Check it
- Open the page's link: the numbers show, and anyone with it open sees updates within a minute.
- In the DocPin app, the page's Data tab shows the current JSON.
GET /v1/pages/$PAGE_ID/datareturns it too.
Variations
- Private dashboard: make the page password-protected or people-only (Pro). The data is then just as private.
- Several sources: have each job
PATCHonly its own keys (sales,support,uptime), so they never overwrite each other. - History or charts: keep a short array in the data, e.g. the last 30 daily values, and replace it each run. Store long history elsewhere: the limit is 1 MB per page.