Documentation

BoardCharts API & Widgets

Push fresh data into any widget over a clean REST API. This page documents every widget type, every endpoint, and includes ready-to-run Python examples — from authentication to live data updates.

Quick start

From zero to a live updating widget in three steps.

1. Get an API key

Sign in, open the Django admin at /admin/api/apikey/, click Add API key, pick a name, and copy the generated key. Treat it like a password.

2. Find the widget you want to update

import requests

API = "https://www.boardcharts.com/api/v2"
KEY = "sk_your_api_key_here"
HEADERS = {"Authorization": f"Bearer {KEY}"}

# 1) Discover dashboards
r = requests.get(f"{API}/dashboards/", headers=HEADERS)
dashboard_key = r.json()["dashboards"][0]["key"]

# 2) Discover widgets in that dashboard
r = requests.get(f"{API}/dashboards/{dashboard_key}/widgets/", headers=HEADERS)
for w in r.json()["widgets"]:
    print(w["key"], w["type"], w["title"])

3. Push fresh data

widget_key = "3f5b354851cb4fb9a1fba419e68c790a"

r = requests.post(
    f"{API}/widgets/{widget_key}/data/",
    headers=HEADERS,
    json={
        "value1": 12450,
        "value2": 9800,        # compare baseline (optional)
        "text1": "today",
        "text2": "yesterday",
    },
)
print(r.json())
# {"success": True, "widget_key": "...", "data": {...},
#  "data_updated_at": "2026-05-08T20:01:30+00:00"}

The dashboard auto-refreshes the widget on its configured refresh_interval — no page reload needed.

Authentication

Every data-mutation request needs an API key. Read endpoints accept the same key.

Send the key one of three ways. Authorization header is preferred.

Authorization: Bearer sk_your_api_key_here
X-Api-Key: sk_your_api_key_here
{
    "api_key": "sk_your_api_key_here",
    "value1": 12450
}
Heads up. Each call updates last_used_at on the key. Rotate or deactivate keys from the admin at /admin/api/apikey/. Never commit keys to git — use environment variables.

Test your key

import os, requests

key = os.environ["BOARDCHARTS_API_KEY"]
r = requests.get(
    "https://www.boardcharts.com/api/v2/me/",
    headers={"Authorization": f"Bearer {key}"},
)
assert r.status_code == 200, r.text
print(r.json())
# {"username": "...", "email": "...", "accounts": [...],
#  "dashboards_count": 19, "widgets_count": 612}

Widget catalog

Six widget types cover almost every dashboard need. Each widget has two payloads: config (presentation, set in admin or via PATCH) and data (dynamic values you push via the data endpoint).

number — Big number with optional comparison

Display one numeric KPI prominently, with an optional secondary value used to compute a percentage delta and an up/down arrow.

Live previewtheme: boardcharts
Revenue today
€12 450
orders: 184
▲27.04%
vs. yesterday
config
FieldTypeDescription
templatestringwidget_number.html (default), widget_number_small.html, widget_number_small_updown.html.
prefixstringSymbol shown before the value (e.g. , $).
suffixstringSymbol shown after the value.
decimal_placesintDefault 2. Used for exact format.
format_number"0" | "1""0" = smart (1.2K, 3.4M, …), "1" = exact with thousands separator.
data
FieldTypeDescription
value1 requirednumberThe headline value.
value2numberComparison value. If present, an up/down % arrow renders.
text1stringSubtitle under the value.
text2stringSubtitle under the comparison value.
requests.post(
    f"{API}/widgets/{key}/data/",
    headers=HEADERS,
    json={
        "value1": 12450.50,
        "value2": 9800.00,
        "text1": "today",
        "text2": "yesterday",
    },
)

text — Headline text

Up to four text slots that render as a heading + supporting lines. No formatting — use html if you need that.

Live previewtheme: boardcharts

Black Friday sale is live

Ends in 2 days — free shipping on orders over €50

data
FieldTypeDescription
text1stringBig headline (h1).
text2stringSubheading (h2).
text3stringParagraph.
text4stringParagraph.
requests.post(
    f"{API}/widgets/{key}/data/",
    headers=HEADERS,
    json={
        "text1": "Black Friday sale is live",
        "text2": "Ends in 2 days — free shipping on orders over €50",
    },
)

html — Free-form HTML

Render arbitrary HTML inside a widget. Useful for embedding tables, iframes, or custom markup. Trusted input only — the markup is rendered as-is.

Live previewtheme: boardcharts
data
FieldTypeDescription
html1stringRaw HTML block.
html2html4stringAdditional HTML blocks, concatenated in order.
requests.post(
    f"{API}/widgets/{key}/data/",
    headers=HEADERS,
    json={
        "html1": '<p><b>12</b> pending orders, <b>4</b> low-stock SKUs.</p>',
    },
)

list — Name / value list

A two-column list. Two ways to push data:

  • Preferred: structured array of {name, value} objects in data.items.
  • Legacy: a single string in config.items, one row per line, name,value.
Live previewtheme: boardcharts
Top products today (revenue)
Nike Air Max 90 — Black
€4 280
Adidas Ultraboost 22
€3 640
Puma Suede Classic
€2 800
Levi's 501 Original
€2 110
Carhartt Beanie
€1 950
data
FieldTypeDescription
itemsarrayList of {"name": str, "value": str|number}.
requests.post(
    f"{API}/widgets/{key}/data/",
    headers=HEADERS,
    json={
        "items": [
            {"name": "Nike Air Max 90 — Black",   "value": "€4 280"},
            {"name": "Adidas Ultraboost 22",      "value": "€3 640"},
            {"name": "Puma Suede Classic",        "value": "€2 800"},
        ],
    },
)

chart — Bar / line / pie / doughnut

A Chart.js-powered chart. config.chart_type picks the visual; data.labels and data.datasets drive the values.

Live previewtheme: boardcharts
Orders — last 7 days
config
FieldTypeDescription
chart_typestringOne of bar, line, pie, doughnut. Default bar.
x_axes_typeintOptional axis hint. 1 = datetime, 2 = time, 3 = currency, 4 = number.
data
FieldTypeDescription
labelsarrayX-axis labels, e.g. ["Mon", "Tue", "Wed"].
datasetsarrayList of [label, [values]] pairs (or {"label": …, "data": [...]} objects).
requests.post(
    f"{API}/widgets/{key}/data/",
    headers=HEADERS,
    json={
        "labels": ["Mon", "Tue", "Wed", "Thu", "Fri"],
        "datasets": [
            ["This week", [120, 145, 132, 170, 198]],
            ["Last week", [110, 128, 125, 152, 180]],
        ],
    },
)

countdown — Live timer

A ticking countdown to a target moment. The dashboard updates the visible timer every second client-side; you only need to set the target.

Live previewtheme: boardcharts
Black Friday sale ends in

--d --:--:--

data
FieldTypeDescription
target_datetimestringTarget moment, format YYYY-MM-DD HH:MM:SS (UTC).
requests.post(
    f"{API}/widgets/{key}/data/",
    headers=HEADERS,
    json={"target_datetime": "2026-12-31 23:59:59"},
)

API reference

All endpoints live under /api/v2/. Read endpoints (GET) and write endpoints both accept the same Authorization: Bearer <key>.

GET /me/ — identify current key

GET /api/v2/me/ api key

Returns the user attached to the API key, plus dashboard/widget counts. Useful as a key-validation probe.

r = requests.get(f"{API}/me/", headers=HEADERS)
print(r.json())
# {"username": "...", "email": "...", "accounts": [...],
#  "dashboards_count": 19, "widgets_count": 612}

GET /dashboards/ — list dashboards

GET /api/v2/dashboards/ api key

List every dashboard the API key has access to.

r = requests.get(f"{API}/dashboards/", headers=HEADERS)
for d in r.json()["dashboards"]:
    print(d["key"], d["name"], f"({d['widgets_count']} widgets)")

GET /dashboards/<key>/widgets/ — list widgets in a dashboard

GET /api/v2/dashboards/<dashboard_key>/widgets/ api key

Returns metadata for each widget — its key, type, title, refresh_interval, and data_updated_at. Use this to find the widget key you'll push data to.

r = requests.get(f"{API}/dashboards/{dashboard_key}/widgets/", headers=HEADERS)
for w in r.json()["widgets"]:
    print(w["type"], w["key"], w["title"])

POST /widgets/<key>/data/ — push fresh data

POST /api/v2/widgets/<widget_key>/data/ api key

Replace the widget's data JSON. The body becomes the new data exactly. The shape depends on the widget type (see the catalog above).

import requests
from requests.adapters import HTTPAdapter, Retry

session = requests.Session()
session.mount("https://", HTTPAdapter(
    max_retries=Retry(total=3, backoff_factor=0.5,
                          status_forcelist=[429, 500, 502, 503, 504])
))

def push(widget_key, payload):
    r = session.post(
        f"{API}/widgets/{widget_key}/data/",
        headers=HEADERS,
        json=payload,
        timeout=10,
    )
    r.raise_for_status()
    return r.json()

push("3f5b354851cb4fb9a1fba419e68c790a", {
    "value1": 12450,
    "value2": 9800,
})

GET /widgets/<key>/render/ — preview the rendered HTML

GET /api/v2/widgets/<widget_key>/render/ public

Returns the live {content, js, data_updated_at} for a widget — the same HTML the dashboard injects on auto-refresh. Useful for previewing or embedding a single widget elsewhere. Respects is_private: private dashboards require an authenticated session.

r = requests.get(f"{API}/widgets/{widget_key}/render/")
payload = r.json()
print(payload["content"])      # widget HTML
print(payload["data_updated_at"])

Errors

All errors return JSON with an error field. Plain HTTP status codes are used.

StatusBodyMeaning
200{"success": true, …}OK.
400plain textMalformed request body — JSON must be an object.
401{"error": "invalid_api_key"}Missing, unknown, or deactivated API key.
403{"error": "forbidden"}Key is valid but doesn't own the target widget/dashboard.
404HTMLWidget or dashboard key not found.
try:
    r = requests.post(
        f"{API}/widgets/{key}/data/",
        headers=HEADERS,
        json={"value1": 100},
        timeout=10,
    )
    r.raise_for_status()
except requests.HTTPError as e:
    if e.response.status_code == 401:
        raise SystemExit("Bad API key — check BOARDCHARTS_API_KEY")
    if e.response.status_code == 403:
        raise SystemExit(f"Key doesn't own widget {key}")
    raise