datatomcp

Reference · updated 4 November 2026

REST API

The same data, definitions and query engine your agents reach over MCP, as plain HTTPS and JSON. Use it from scripts, n8n, Make, Zapier or your own backend. Every answer names the dataset version it came from, and every call shows up in the workspace audit.

Quick start

  1. In the app, open Account → API keys and create a key. It starts with dtm_ and is shown once.
  2. Find a dataset id with GET /datasets, or copy it from the dataset page.
  3. Ask for totals. This one uses the team's approved paid_revenue definition:
curl https://app.datatomcp.com/v1/datasets/DATASET_ID/query \
  -H "Authorization: Bearer $DATATOMCP_KEY" \
  -H "content-type: application/json" \
  -d '{
    "group_by": ["region"],
    "metrics": [{ "metric": "paid_revenue" }, { "fn": "count", "as": "orders" }],
    "where": [{ "column": "order_date", "op": "between", "value": ["2026-01-01", "2026-09-30"] }],
    "order_by": [{ "column": "paid_revenue", "dir": "desc" }]
  }'

The answer:

{
  "rows": [
    { "region": "EU", "paid_revenue": 412870.5, "orders": 1834 },
    { "region": "US", "paid_revenue": 298114, "orders": 1310 }
  ],
  "meta": {
    "dataset_id": "ds_8f2k…",
    "version": 14,
    "refreshed_at": "2026-11-04T07:12:09.000Z",
    "rows_returned": 2,
    "rows_matched": 2,
    "truncated": false,
    "metrics": [{ "name": "paid_revenue", "approved_by": "Ana" }],
    "view_url": "https://app.datatomcp.com/app/datasets/ds_8f2k…?q=…"
  }
}

Authentication

Send the key in the Authorization header: Authorization: Bearer dtm_…. A key acts as the person who created it, with their role in each workspace (owner, editor or viewer). You can limit a key to some workspaces from the Connect page; it then sees nothing else. Keys can't create or revoke other keys: do that in the app. Revoked keys stop working at once.

Agents in Claude, ChatGPT, Cursor and other MCP clients don't need a key: they connect to https://app.datatomcp.com/mcp and sign in with OAuth.

The query

Queries are JSON, not SQL. Column names are checked against the dataset, values are always bound as parameters, and agents get read-only access. There are two modes:

  • Rows: leave out metrics. Use select, where, order_by, limit (up to 1,000) and offset.
  • Totals: add metrics, and group_by for one row per group.

Filters

{ "column": "status", "op": "in", "value": ["paid", "shipped"] }. Operators: eq, neq, gt, gte, lt, lte, in, not_in, contains, starts_with, is_null, not_null, between. between includes both ends; contains and starts_with ignore case. Call GET /datasets/{id}/values/{column} first to filter on the values that are really there.

Metrics and definitions

A metric is either a calculation, { "fn": "sum", "column": "amount", "as": "revenue" } with fn one of count, count_distinct, sum, avg, min or max, or a saved definition by name: { "metric": "paid_revenue" }. A definition is the team's agreed meaning of a number, approved by a person, and the answer says who approved it. A metric can carry its own where, so "revenue" and "refunded revenue" fit in one call.

Linked datasets

When a dataset links to another (orders to customers, say), the linked columns work as alias.column in select, where and group_by: "group_by": ["customers.segment"]. Chained links work too (orders → customers → regions). GET /datasets/{id} lists them.

Versions

Every refresh makes a new version. Pass "version": 14 on a query to read the data an earlier answer came from, so a follow-up question doesn't change under you. Free keeps 2 versions, Pro 20, for 30 days.

What every answer carries

dataset_id
The dataset that answered.
version
The dataset version the result came from.
refreshed_at
When this version was fetched or uploaded.
rows_returned
Rows in this response.
rows_matched
Rows that matched before the limit.
truncated
True when more rows matched than the limit returned.
metrics
The saved definitions the query used, with who approved them.
linked
unmatched
Rows whose link value found no row in the linked dataset.
view_url
A page where a person can check the rows behind the number.
rows_counted
Totals: rows of the dataset the numbers were computed over, after the filters.
units
Unit per result column, from the definition or the column's meaning.
method
How the numbers were computed, in one line.
freshness
warnings
What affects the number: duplicate rows, empty values in the columns used, sums of columns that don't add up.

Datasets

Add, describe, refresh, change and remove datasets.

GET /datasets

List datasets. Datasets of every workspace the key can reach.

ParameterInTypeDescription
workspace_idquerystringOnly this workspace.

POST /datasets

Add a dataset. Upload a CSV or Excel file (raw body or multipart field `file`), or send JSON with a public CSV link or a Google Sheet you shared through the app.

ParameterInTypeDescription
workspace_idquerystringUpload only: the target workspace.
sheetquerystringUpload only: which Excel sheet.
Body fieldTypeDescription
source_urlstringA public CSV or Excel link (Google Sheets publish link, Dropbox, S3...).
google_sheetobject
namestring
workspace_idstring
sheetstring

Uploads: send the file as the raw body (content-type: text/csv) or as multipart field file. Excel files work the same way.

GET /datasets/{id}

Describe a dataset. Columns with types and examples, the import report, links, definitions and glossary.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.

PATCH /datasets/{id}

Rename, switch auto-sync or change column types.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.
Body fieldTypeDescription
namestring
auto_syncbooleanGoogle Sheets and links: sync within minutes (Pro).
column_typesobjectRe-imports the data with these types.

DELETE /datasets/{id}

Delete a dataset. Owners and editors only. Removes every version.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.

POST /datasets/{id}/refresh

Refresh from the source. Links and Google Sheets are fetched again. Uploads take the new file as the body, like POST /datasets. Each refresh is a new version.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.

Uploads: send the file as the raw body (content-type: text/csv) or as multipart field file. Excel files work the same way.

GET /datasets/{id}/docs.md

Dataset docs as Markdown. Columns, definitions and example calls, ready to paste into an agent's instructions.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.

PUT /datasets/{id}/columns/{column}

Describe a column. Editors only. What the column means, its unit, its role and whether it adds up. Kept across refreshes; agents read it in describe_dataset and the answers' units.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.
columnpathstringColumn id.
Body fieldTypeDescription
descriptionstring
unitstringnull
roleid | date | dimension | metricmetric needs a number column.
summableboolean

DELETE /datasets/{id}/columns/{column}

Back to the drafted meaning.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.
columnpathstringColumn id.

Query

Rows and totals, distinct values and versions. One engine shared with the MCP server.

POST /datasets/{id}/query

Query rows or totals. Without metrics: rows. With metrics: totals, per group with group_by. Saved definitions are used as { "metric": "name" }. Every call is recorded in the workspace audit.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.
Body fieldTypeDescription
selectstring[]Columns to return (rows mode). Leave out for all columns.
whereFilter[]
group_bystring[]
grainday | week | month | quarter | yearGroup date and timestamp columns in group_by by this period (weeks start on Monday). Each group is labelled with the period's first day.
metricsMetric[]With metrics the query returns totals, per group when group_by is set.
order_byobject[]
limitinteger
offsetinteger
versionintegerPin the query to a dataset version, so follow-up calls read the same data.

GET /datasets/{id}/values/{column}

Distinct values of a column. The real values with their counts, so filters match "France" and not "FR".

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.
columnpathstringColumn id.
limitqueryinteger
versionqueryintegerRead an older version still kept (GET /datasets/{id}/versions).

GET /datasets/{id}/versions

Versions still kept.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.

Definitions

Saved metrics and the glossary agents read.

GET /datasets/{id}/metrics

Definitions of a dataset.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.
draftsquery1Include drafts waiting for approval.

PUT /datasets/{id}/metrics/{name}

Add or replace a definition. Editors only. A definition saved over the API is approved by the key's owner.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.
namepathstringsnake_case name, like paid_revenue.
Body fieldTypeDescription
descriptionstring
fncount | count_distinct | sum | avg | min | max
columnstring
whereFilter[]
unitstring
aliasesstring[]Other names that answer to this definition, like sales.
directionhigher_is_better | lower_is_betterGuessed from the name when left out (costs and churn: lower is better).

DELETE /datasets/{id}/metrics/{name}

Remove a definition.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.
namepathstringsnake_case name, like paid_revenue.

POST /datasets/{id}/metrics/{name}/approve

Approve a draft. Editors only. From then on agents get it, with the approver's name.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.
namepathstringName of a drafted definition.

GET /workspaces/{id}/overview

What a workspace can answer. Every dataset with a one-line description, its columns' meanings and freshness; the approved definitions; the links between datasets; the glossary; and five example questions with the exact call that answers each. Same as the MCP workspace_overview tool.

ParameterInTypeDescription
idpathstringWorkspace id, from GET /workspaces.

GET /workspaces/{id}/definitions

All definitions and the glossary of a workspace.

ParameterInTypeDescription
idpathstringWorkspace id, from GET /workspaces.
draftsquery1Include drafts waiting for approval.

Charts

A chart from a query, or from your own chart spec.

POST /datasets/{id}/chart

Chart from a query. Runs the query like /query (same checks, same audit), then draws its rows.

ParameterInTypeDescription
idpathstringDataset id, from GET /datasets.
Body fieldTypeDescription
chartline | area | columns | bar | donut | treemap | waffle | funnel | pareto | scatter | heatmap | histogram | kpi | table | variance | bullet | waterfall | scorecard | combo | radar | candlestick | gauge | box | dotPlot
xstringResult column for the x axis or the categories (a group_by column).
ystring[]Result columns with the values.
seriesstringResult column whose values become separate series.
titlestringWrite the takeaway, like "EU revenue doubled in Q3".
subtitlestring
sourcestring
notestring
formatnumber | percent | currency | compact
currencystring
highlightstring[]Series or categories to highlight; the rest are drawn in grey.
grainday | week | month | quarter | yearGroups the dates of a time chart by period, and the query's date group columns with them.
modestacked | grouped | percentcolumns and bar: stacked (default), side by side, or each adding up to 100%.
piebooleandonut: draw a full pie.
invertbooleanLower is better for the values drawn (costs). Definitions marked lower is better set it for their own columns.
wordsobjectReplace the words a chart draws by itself, e.g. { "plan": "Budget", "other": "Autres" }.
themedatatomcp-dark | datatomcp-light | editorial-light | editorial-dark | brand-light | brand-dark
widthinteger
outputsvg | figure | spec | csvLeave out for JSON with spec, svg and meta.
queryQueryA constrained JSON query. Column names are checked against the schema, values are bound as parameters, and every query has a limit and a timeout.
versioninteger
curl https://app.datatomcp.com/v1/datasets/DATASET_ID/chart \
  -H "Authorization: Bearer $DATATOMCP_KEY" -H "content-type: application/json" \
  -d '{ "chart": "columns", "x": "month", "title": "EU revenue doubled in Q3",
        "query": { "group_by": ["month"], "metrics": [{ "metric": "paid_revenue" }] },
        "output": "svg" }' > chart.svg

POST /charts/render

Draw your own chart spec. Any of the 52 chart types of @datatomcp/charts from a spec you send. No data access.

Body fieldTypeDescription
specobject
themedatatomcp-dark | datatomcp-light | editorial-light | editorial-dark | brand-light | brand-dark
widthinteger
outputsvg | figure | csv

Dashboards

Stories (slides) and boards (one page) built from queries; refresh re-runs them exactly.

GET /dashboards

List dashboards. The dashboards of a workspace (the current one when workspace_id is left out), with owner, type and last refresh.

ParameterInTypeDescription
workspace_idquerystring

POST /dashboards

Create a dashboard. A story takes slides, each with an optional title and blocks; a board takes blocks. Blocks: chart { dataset_id, query, chart }, kpi { dataset_id, query: { metrics }, title }, text { text }, commentary { prompt } (written from the numbers on your own AI key at each refresh), shape { shape }. Layout is optional: blocks are placed for you. Charts are drawn at once. Only the workspace's own datasets can be used.

Body fieldTypeDescription
workspace_idstring
typestory | board
titlestring
subtitlestring
modelight | dark
slidesobject[]
blocksobject[]

GET /dashboards/{id}

Read a dashboard. Its blocks with their drawn chart specs, comments and who is editing what.

ParameterInTypeDescription
idpathstringDashboard id, from GET /dashboards.

DELETE /dashboards/{id}

Delete a dashboard. Only the person who made it, or the workspace owner.

ParameterInTypeDescription
idpathstringDashboard id, from GET /dashboards.

POST /dashboards/{id}/refresh

Refresh a dashboard. Re-runs every saved query on the latest data, exactly; no AI key needed. Commentary is rewritten on your own AI key (Account → AI models); without one it keeps its last text and the answer says "API key not provided". Every refresh shows in Audit → Dashboard refreshes.

ParameterInTypeDescription
idpathstringDashboard id.

Account

Workspaces and usage.

GET /workspaces

Workspaces the key can reach.

GET /usage

Today's usage.

Limits

FreePro
Datasets325
Rows per dataset100,0002,000,000
File size20 MB200 MB
Queries per day1,00050,000
Rows per response1,0001,000
Versions kept220

Daily limits reset at 00:00 UTC. GET /usage shows today's count. Queries stop after a few seconds; add filters if one times out.

Errors

Errors are JSON with a stable code and a message written to be shown to a person or an agent: { "error": { "code": "unknown_column", "message": "…" } }.

CodeStatusMeaning
invalid_request400The body or a parameter is wrong. The message names the field.
invalid_query400The query doesn't fit the dataset, like a sum of a text column.
unknown_column400A column that isn't in the dataset. The message lists close matches.
unauthorized401No API key, or a revoked one.
forbidden403The key's owner has no access, or the key is limited to other workspaces.
not_found404No such dataset, workspace or definition, or no access to it.
upload_too_large413The file is over your plan's size limit.
row_limit413The file has more rows than your plan allows.
query_limit429Today's query limit is used. It resets at 00:00 UTC.
query_timeout408The query took too long. Add filters or group by fewer columns.
fetch_failed400The source link or sheet couldn't be read. The dataset keeps its last version.

Something missing or wrong? Write to [email protected]. For agents, see the MCP setup guides; for charts, the chart library.