docpin
View as Markdown

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=sales

The 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

Variations