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
}
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.
| Field | Type | Description |
|---|---|---|
template | string | widget_number.html (default), widget_number_small.html, widget_number_small_updown.html. |
prefix | string | Symbol shown before the value (e.g. €, $). |
suffix | string | Symbol shown after the value. |
decimal_places | int | Default 2. Used for exact format. |
format_number | "0" | "1" | "0" = smart (1.2K, 3.4M, …), "1" = exact with thousands separator. |
| Field | Type | Description |
|---|---|---|
value1 required | number | The headline value. |
value2 | number | Comparison value. If present, an up/down % arrow renders. |
text1 | string | Subtitle under the value. |
text2 | string | Subtitle 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.
| Field | Type | Description |
|---|---|---|
text1 | string | Big headline (h1). |
text2 | string | Subheading (h2). |
text3 | string | Paragraph. |
text4 | string | Paragraph. |
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.
| Field | Type | Description |
|---|---|---|
html1 | string | Raw HTML block. |
html2 – html4 | string | Additional 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 indata.items. - Legacy: a single string in
config.items, one row per line,name,value.
| Field | Type | Description |
|---|---|---|
items | array | List 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.
| Field | Type | Description |
|---|---|---|
chart_type | string | One of bar, line, pie, doughnut. Default bar. |
x_axes_type | int | Optional axis hint. 1 = datetime, 2 = time, 3 = currency, 4 = number. |
| Field | Type | Description |
|---|---|---|
labels | array | X-axis labels, e.g. ["Mon", "Tue", "Wed"]. |
datasets | array | List 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.
| Field | Type | Description |
|---|---|---|
target_datetime | string | Target 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
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
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
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
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
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.
| Status | Body | Meaning |
|---|---|---|
200 | {"success": true, …} | OK. |
400 | plain text | Malformed 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. |
404 | HTML | Widget 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