Overview
The Dashboard Data API v2 returns the numbers behind a DataTile dashboard as JSON, so you can pull them into your own BI tool, spreadsheet, or pipeline. One call returns a whole dashboard tab: its controls, what each control currently has selected, and the data of every chart and table on that tab.
You can also set the controls in the request, the same way a viewer picks an option on the dashboard, and get the recalculated data back. Whatever the dashboard does with that selection behind the scenes, you only deal with control and option IDs.
|
You want to… |
Call |
|---|---|
|
Get the ID of the dashboard your key belongs to |
|
|
List a dashboard's tabs |
|
|
Get a tab's controls and all its chart/table data |
|
|
Same, with different control selections |
the tab call plus |
The API is read-only. It returns data as the dashboard owner sees it, and it never changes what other viewers of the dashboard see.
There is also an older API under /api/v2/dashboards/{hash}/api/… that returns one widget per call and cannot change control selections. This guide covers the newer /api/dashboard-data/v2 API; see "Migrating from the older API" at the end.
Why the API serves dashboards, not the database
The API gives access to dashboards, never directly to the database behind them. This is deliberate:
-
You get the few numbers you need. A DataTile database holds a whole study and is vast. An integration usually needs a handful of cuts or KPIs, and a dashboard tab is exactly that set, already prepared.
-
A dashboard can combine several sources. A database is a single source. A dashboard can draw on several data sources, connected the way its owner designed, and the API returns the combined result.
-
The research complexity stays inside DataTile. Calculations, significance testing and the links between sources are handled by the dashboard. You receive simple, BI-ready tables and do not need to rebuild any of that logic.
-
The data owner stays in control. The dashboard is the security boundary: it exposes only what the data owner chooses to make available, and it is where the owner sets the rules for how the data is used: which tabs and widgets are visible, whether API access is on, and who holds the key. The owner can withdraw access at any time by switching API access off or regenerating the key.
Before you start
You need two things: the address of your DataTile instance and the dashboard's API key.
Base URL
|
Environment |
Base URL |
|---|---|
|
DataTile Cloud |
|
|
Your own instance |
the address you open DataTile at, e.g. |
All paths in this guide start with {BASE_URL}/api/dashboard-data/v2.
Try it on the demo dashboard
To experiment before setting up your own dashboard, use the public demo on DataTile Cloud. Its key is 2414e5bd-0fcb-4a4d-815a-ac61b6915243. It opens only the read-only demo dashboard "Interactive inferences" (a07329df9a0c472db14ad6bbd8a503d3), and every example in this guide works with it as written.
↗ Open the demo dashboard in a new window: https://cloud.datatile.eu/view/e764fc96fba84013a3e1eb8683c23f04
Right-click the link and choose Open Link in New Window, then keep the dashboard next to this guide while you read. No login is needed.
On the dashboard, the OnWater, Vector Cola and LiquiBoost buttons are the brand control (5756), and the "Any age" drop-down is the age_category control (109711). Pick Vector Cola there and the charts change the same way as with the API call ?5756=441270. The text and image above the charts are not in the API response, which returns charts and tables only.
Get the dashboard API key
Each dashboard has its own key. Only the dashboard owner can switch API access on and copy the key.
-
Open the dashboard as its owner.
-
Click the
{api}icon in the dashboard header. -
Switch on API access.
-
Click Copy API key. The key is not shown on screen; it goes to your clipboard.
Regenerate API key issues a new key and immediately stops the old one from working. Every integration using the old key loses access until you give it the new one. This cannot be undone.
Send the key with every request
Put the key in the token HTTP header. X-Auth-Token works too. An Authorization: Bearer header is not accepted.
curl "https://cloud.datatile.eu/api/dashboard-data/v2/dashboards" \
-H "token: $DASHBOARD_KEY"
What the key gives access to
-
One dashboard. Keys are issued per dashboard: a key opens only the dashboard it was issued for. To read several dashboards, switch API access on for each one and use each dashboard's own key. Your personal DataTile API token (the one used for the Data Collector API) does not work here.
-
The owner's view of the data. Numbers are calculated with the dashboard owner's permissions. Anyone holding the key sees what the owner sees, so treat it like a password: keep it out of source code, shared spreadsheets and URLs.
-
Visible content only. Hidden tabs and widgets hidden in view mode are not returned.
Finding the IDs you need
You never need to copy an ID from the DataTile interface: each call returns the IDs for the next one. Start with /dashboards, which returns the ID of the one dashboard your key belongs to. List that dashboard's tabs, then call a tab to get its control, option and widget IDs.
|
ID |
Returned by |
Looks like |
Used in |
|---|---|---|---|
|
Dashboard ID |
|
|
path: |
|
Tab ID |
|
|
path: |
|
Control ID |
|
|
query parameter name |
|
Option ID |
|
|
query parameter value |
|
Widget ID |
|
|
match widgets between calls |
The ID in a dashboard's public or embed link (/view/…, /dashboard/embedded/…) is a share-link ID, not the dashboard ID. Calling the API with it returns 404 dashboard not found.
Widget and control names can be edited by the owner at any time, so match on IDs in code, not names.
Endpoints
Three GET endpoints, all under {BASE_URL}/api/dashboard-data/v2, all authenticated with the token header. Responses are application/json.
Get the key's dashboard
GET /dashboards returns the dashboard your key belongs to. The response is a list, but because keys are issued per dashboard it always holds exactly one item.
[
{ "id": "a07329df9a0c472db14ad6bbd8a503d3", "name": "Interactive inferences" }
]
Get a dashboard
GET /dashboards/{dashboardId} returns the dashboard's name and its visible tabs.
{
"id": "a07329df9a0c472db14ad6bbd8a503d3",
"name": "Interactive inferences",
"tabs": [
{ "id": "342bbc0c1b534d5baa05c10d298c027c", "name": "Brand KPIs", "visible": true }
]
}
visible is always true: hidden tabs are left out.
Get a tab's controls and data
GET /dashboards/{dashboardId}/tabs/{tabId} calculates the tab and returns its controls and the data of every chart and table on it. Add query parameters to change control selections first (see "Changing control selections" below).
{
"id": "342bbc0c1b534d5baa05c10d298c027c",
"name": "Brand KPIs",
"controls": [
{
"id": "5756",
"name": "brand",
"mode": "single",
"options": [
{ "id": "441240", "name": "OnWater", "selected": true },
{ "id": "441270", "name": "Vector Cola", "selected": false },
{ "id": "441280", "name": "LiquiBoost", "selected": false }
]
}
],
"widgets": [
{
"id": "5751",
"name": "brand-awareness",
"data": [
["split_name","split_id","row_name","row_id","row_alias","column_name","column_id","column_alias","metric_name","metric_id","value","s10e1_value","s10e2_value"],
["Value","n","TOM","be_5756_441240_441241$vpt",null,"2017'Q2","07407109-b6d8-47db-d9e5-3b26877e6774",null,"Vert%","vpt",0.623529412,null,null],
["Value","n","TOM","be_5756_441240_441241$vpt",null,"2017'Q3","bd3ca3a4-b1a4-114a-0e2f-8963ebb45c18",null,"Vert%","vpt",1.158823529,"high",null]
]
}
]
}
|
Field |
Meaning |
|---|---|
|
|
|
|
|
the selection the data was calculated with |
|
|
filled for button, drop-down, list, grouped drop-down and accordion controls; empty for other control types |
|
|
charts and tables only; text, image and other widget types are not returned |
|
|
a table: header row first, then one row per value (see "Reading widget data" below) |
A tab is calculated in full on every call. Tabs with many widgets take longer; the server gives up after 150 seconds.
Reading widget data
widgets[].data is a long-format table: the first row holds the 13 column names, and every following row is one number from the chart or table. A chart with 4 rows × 8 columns × 1 metric gives 32 data rows. A widget with nothing to show returns the header row only.
The columns are the same for every widget type:
|
Column |
What it holds |
Example |
|---|---|---|
|
|
name of the split (breakdown) the value belongs to; |
|
|
|
ID of that split |
|
|
|
row label as shown on the dashboard |
|
|
|
unique row ID; may end in |
|
|
|
short alias the owner gave the row, else |
|
|
|
column label as shown |
|
|
|
unique column ID |
|
|
|
short alias the owner gave the column, else |
|
|
|
metric label |
|
|
|
metric code |
|
|
|
the number; see the notes below |
|
|
|
significance result: |
|
|
|
additional significance marker as text, when the widget's significance settings produce one, else |
|
Notes on value:
-
It is a JSON number or
null, never a string. -
It is the unrounded number. Round it yourself; the dashboard's display precision is not applied.
-
Percentages are in percent units:
13.87means 13.87 %. -
nullmeans the cell has no number: it is empty, or the value could not be calculated (for example, a base of zero).
Significance columns are filled only when significance testing is switched on for the widget. Which base a value is compared against (total, previous wave, another column) is set on the widget in DataTile and is not part of the response.
Rows hidden in the dashboard view are not returned.
Changing control selections
Controls are the dashboard's inputs: buttons, drop-downs, lists and accordions. The owner decides what each control does, widget by widget, so one control can play different roles on different widgets:
-
filter the data a widget is calculated on;
-
swap a widget's dimension, for example which brand's rows a chart shows;
-
show or hide widgets or elements;
-
change templated text.
The API hides that wiring. You set a control the way a viewer would and get back whatever the dashboard calculates for that selection, even when the widgets behind it draw on different data sources.
What that means for your code:
-
Don't assume a widget keeps the same rows and columns across selections. On the demo dashboard the brand control swaps the rows, and the row IDs carry the selected option:
be_5756_441240_…for OnWater,be_5756_441270_…for Vector Cola. -
The list of widgets can change when a control shows or hides widgets.
-
Effects on text are not visible, because text widgets are not returned.
To set a selection, add one query parameter per control to the tab call: the control ID as the name, the option ID as the value.
GET /dashboards/{dashboardId}/tabs/{tabId}?5756=441270
This returns the tab as if a viewer had picked option 441270 (Vector Cola) in control 5756 (brand). Every widget on the tab is recalculated.
Control state: send only what you change
You don't need to set every control, or list every option, in the call. Send only the controls you want to change. Every control you leave out keeps its default state, the selection the dashboard opens with, and DataTile applies those defaults silently: no error or warning says a control was left out.
In the example above, ?5756=441270 sets only the brand. age_category (109711) is not in the call, so the data is calculated with its default, "Any age". A call with no parameters at all returns the tab exactly as the dashboard opens.
Because the defaults apply without notice, a change the owner makes to a control's default changes your results too. To pin a selection, send that control explicitly.
Rules
-
Each call starts from the dashboard's defaults. Selections are not remembered between calls, and your calls never change what dashboard viewers see.
-
The options you pass replace the control's default selection. On the demo dashboard,
?109711=608664givesage_category= 16-20 only; the default "Any age" is dropped. -
Several options for one control: separate them with a comma,
?109711=608664,608665, or a semicolon,?109711=608664;608665, or repeat the parameter,?109711=608664&109711=608665. -
Several controls: one parameter each,
?5756=441270&109711=608666,608667. -
Default fallback options such as "Any age" are dropped when you combine them with specific options:
?109711=608669,608664gives 16-20 only. -
Single-select controls take exactly one option. Passing two or more is rejected with 400.
-
Accordion controls use the combined
section-optionID exactly as returned, e.g.?5900=12-34. -
An empty value asks for nothing selected.
?{controlId}=clears a multi-select control that allows an empty selection. Controls set to require at least one option fall back to their default instead:?109711=keeps "Any age" on the demo dashboard. A parameter with no=(?109711) means the same as?109711=. -
Only control IDs are accepted as parameters. Every query parameter must be the ID of a control shown on this tab. Anything else is rejected with 400
unknown control: a mistyped ID, a control hidden on the tab, a control from another tab, or a parameter your HTTP client adds on its own, such as a cache-busting_=…. -
Unknown options are rejected. An option ID the control does not list returns 400
unknown option. Option IDs are checked only for controls that list options (seecontrols[].optionsabove); values for other control types are not checked. -
A control only changes the widgets it is linked to. Selecting an option in a control that isn't linked to any widget on the tab returns the same numbers.
Read controls[].options[].selected in the response to see the selection the data was calculated with, including the defaults of controls you did not set.
Pagination and limits
-
There is no pagination: one call returns every chart and table on the tab in full.
-
There is no call for a single widget; take the one you need from
widgetsby itsid. -
A tab calculation stops after 150 seconds.
-
No rate limit is published. Each tab call runs the full calculation, so cache results and avoid polling more often than your data changes.
Worked examples
Both examples read the key from the DASHBOARD_KEY environment variable, so it never lands in the script itself. They use the public demo key and dashboard, so you can run them as written; swap in your own key and IDs afterwards.
curl
export DASHBOARD_KEY='2414e5bd-0fcb-4a4d-815a-ac61b6915243' # public demo key; use your own for your dashboards
BASE=https://cloud.datatile.eu/api/dashboard-data/v2
# 1. Dashboard ID
curl -s "$BASE/dashboards" -H "token: $DASHBOARD_KEY"
# 2. Tab IDs
curl -s "$BASE/dashboards/a07329df9a0c472db14ad6bbd8a503d3" -H "token: $DASHBOARD_KEY"
# 3. Tab data with default selections
curl -s "$BASE/dashboards/a07329df9a0c472db14ad6bbd8a503d3/tabs/342bbc0c1b534d5baa05c10d298c027c" \
-H "token: $DASHBOARD_KEY"
# 4. Same tab with brand = Vector Cola
curl -s "$BASE/dashboards/a07329df9a0c472db14ad6bbd8a503d3/tabs/342bbc0c1b534d5baa05c10d298c027c?5756=441270" \
-H "token: $DASHBOARD_KEY"
Python: export every tab to CSV
Save as dashboard_export.py. Needs requests (pip install requests). Writes one CSV row per value, with the tab and widget it came from.
import csv
import os
import sys
import requests
BASE_URL = "https://cloud.datatile.eu/api/dashboard-data/v2"
session = requests.Session()
session.headers["token"] = os.environ["DASHBOARD_KEY"]
def get(path, **params):
response = session.get(BASE_URL + path, params=params, timeout=180)
if not response.ok:
sys.exit(f"{response.status_code}: {response.json()['message']}")
return response.json()
# 1. The key opens exactly one dashboard
dashboard_id = get("/dashboards")[0]["id"]
# 2. Its visible tabs
dashboard = get(f"/dashboards/{dashboard_id}")
# 3. Every tab, with default selections, written to one CSV
writer = csv.writer(sys.stdout)
writer.writerow(["tab", "widget_id", "widget"] + [
"split_name", "split_id", "row_name", "row_id", "row_alias",
"column_name", "column_id", "column_alias", "metric_name", "metric_id",
"value", "s10e1_value", "s10e2_value"])
for tab in dashboard["tabs"]:
data = get(f"/dashboards/{dashboard_id}/tabs/{tab['id']}")
for widget in data["widgets"]:
header, *rows = widget["data"]
for row in rows:
writer.writerow([tab["name"], widget["id"], widget["name"]] + row)
Run it with python dashboard_export.py > dashboard.csv. An error stops the script with the status and the server's message, for example 400: unknown control '5757'.
To change selections, pass control IDs as parameters. Python identifiers cannot be digits, so unpack a dict; a list value sends the parameter once per option:
data = get(f"/dashboards/{dashboard_id}/tabs/{tab_id}", **{"109711": ["608664", "608665"]})
Errors and troubleshooting
Errors come back as JSON with the HTTP status in statusCode and the reason in message. Authentication errors also carry available and failed, kept for older clients; you can ignore them.
|
Status |
|
Likely cause |
What to do |
|---|---|---|---|
|
400 |
|
a query parameter that is not a control shown on this tab: a mistyped or hidden control, or a parameter your client adds, such as |
take control IDs from |
|
400 |
|
the control does not list that option |
take option IDs from that control's |
|
400 |
|
several options sent to a single-select control |
send one option; call the tab once per option to compare them |
|
401 |
|
no |
send the key in the |
|
403 |
|
key not recognised: mistyped, regenerated, or API access switched off |
copy the key again from the |
|
404 |
|
the dashboard ID is not the one this key belongs to, often a share-link ID |
take the ID from |
|
404 |
|
tab ID is wrong, or the tab is hidden |
take the ID from |
|
500 |
|
a failure on the server |
retry later; ask the owner to split a heavy tab |
Example bodies:
{ "message": "unknown option '999999' for control '109711'", "statusCode": 400 }
{ "available": false, "failed": true, "message": "Authentication failed", "statusCode": 403 }
Data looks wrong
-
Numbers differ from the dashboard. Compare
controls[].options[].selectedwith the selections on the dashboard. The API starts from the defaults, not from the options you last picked in the browser. -
A widget is missing. Only charts and tables are returned, only those visible in view mode, and a control may hide a widget for the current selection.
-
A widget has a header row and nothing else. The widget has no data for the current selection.
-
valueisnull. The cell is empty or cannot be calculated; treat it as missing. -
The selection did nothing. The call succeeded, so the control and option IDs are valid. The control may not be linked to the widget you are looking at, or it may only change things the API does not return, such as text.
Migrating from the older API
The older API (/api/v2/dashboards/{hash}/api/…) still works and uses the same key and token header. The new API returns the same 13-column widget data, so most code only needs new URLs.
|
Older call |
New equivalent |
|---|---|
|
|
|
|
|
|
|
|
the same tab call → |
What changes for your code:
-
One call per tab instead of one per widget.
-
Widget IDs are strings (
"5751"), not numbers (5751). -
You can set control selections; the older API always used the defaults.
-
The tab response also lists the controls and their options.
Not available yet
-
Fetching a single widget without calculating the whole tab.
-
Options for control types other than button, drop-down, list and accordion (for example, date or range controls).
-
Display formatting: number precision, colours, chart type.
-
Which base a significance result compares against.