DataTile Knowledge Base
Breadcrumbs

Retrieving data from Dashboard Data API

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

GET /api/dashboard-data/v2/dashboards

List a dashboard's tabs

GET /api/dashboard-data/v2/dashboards/{dashboardId}

Get a tab's controls and all its chart/table data

GET /api/dashboard-data/v2/dashboards/{dashboardId}/tabs/{tabId}

Same, with different control selections

the tab call plus ?{controlId}={optionId}

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

https://cloud.datatile.eu

Your own instance

the address you open DataTile at, e.g. https://datatile.example.com

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.

  1. Open the dashboard as its owner.

  2. Click the {api} icon in the dashboard header.

  3. Switch on API access.

  4. 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.

Bash
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

GET /dashboards

a07329df9a0c472db14ad6bbd8a503d3 (32 hex characters)

path: /dashboards/{dashboardId}

Tab ID

GET /dashboards/{dashboardId}

342bbc0c1b534d5baa05c10d298c027c

path: /tabs/{tabId}

Control ID

GET …/tabs/{tabId} → controls[].id

"5756" (digits, sent as a string)

query parameter name

Option ID

GET …/tabs/{tabId} → controls[].options[].id

"441240"; accordion options: "12-34" (section-option)

query parameter value

Widget ID

GET …/tabs/{tabId} → widgets[].id

"5751"

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.

JSON
[
  { "id": "a07329df9a0c472db14ad6bbd8a503d3", "name": "Interactive inferences" }
]

Get a dashboard

GET /dashboards/{dashboardId} returns the dashboard's name and its visible tabs.

JSON
{
  "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).

JSON
{
  "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

controls[].mode

single: one option at a time. multi: any number.

controls[].options[].selected

the selection the data was calculated with

controls[].options

filled for button, drop-down, list, grouped drop-down and accordion controls; empty for other control types

widgets

charts and tables only; text, image and other widget types are not returned

widgets[].data

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

split_name

name of the split (breakdown) the value belongs to; Value when the widget has no split

Value

split_id

ID of that split

n

row_name

row label as shown on the dashboard

TOM

row_id

unique row ID; may end in $ + metric ID

be_5756_441240_441241$vpt

row_alias

short alias the owner gave the row, else null

null

column_name

column label as shown

2017'Q3

column_id

unique column ID

bd3ca3a4-b1a4-114a-0e2f-8963ebb45c18

column_alias

short alias the owner gave the column, else null

null

metric_name

metric label

Vert%

metric_id

metric code

vpt

value

the number; see the notes below

1.158823529

s10e1_value

significance result: high or low when the value differs significantly from its comparison base, else null

high

s10e2_value

additional significance marker as text, when the widget's significance settings produce one, else null

null

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.87 means 13.87 %.

  • null means 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=608664 gives age_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,608664 gives 16-20 only.

  • Single-select controls take exactly one option. Passing two or more is rejected with 400.

  • Accordion controls use the combined section-option ID 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 (see controls[].options above); 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 widgets by its id.

  • 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

Bash
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.

Python
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:

Python
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

message

Likely cause

What to do

400

unknown control '…'

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 controls[].id of the same tab; stop the client adding extra parameters

400

unknown option '…' for control '…'

the control does not list that option

take option IDs from that control's options[].id

400

control '…' accepts one option, got 2

several options sent to a single-select control

send one option; call the tab once per option to compare them

401

No authentication

no token header, or the key sent as Authorization: Bearer

send the key in the token header

403

Authentication failed

key not recognised: mistyped, regenerated, or API access switched off

copy the key again from the {api} menu; ask the owner whether API access is on

404

dashboard not found

the dashboard ID is not the one this key belongs to, often a share-link ID

take the ID from GET /dashboards

404

tab not found

tab ID is wrong, or the tab is hidden

take the ID from GET /dashboards/{dashboardId}

500

Server error

a failure on the server

retry later; ask the owner to split a heavy tab

Example bodies:

JSON
{ "message": "unknown option '999999' for control '109711'", "statusCode": 400 }

JSON
{ "available": false, "failed": true, "message": "Authentication failed", "statusCode": 403 }

Data looks wrong

  • Numbers differ from the dashboard. Compare controls[].options[].selected with 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.

  • value is null. 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

GET /api/v2/dashboards/{hash}/api (tabs)

GET /api/dashboard-data/v2/dashboards/{dashboardId} → tabs

GET /api/v2/dashboards/{hash}/api/{tabHash} (widget list)

GET …/dashboards/{dashboardId}/tabs/{tabId} → widgets[].id, name

GET /api/v2/dashboards/{hash}/api/{tabHash}/{widgetId} (one widget)

the same tab call → widgets[].data

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.