Metrics
A metric is a numeric time series. Field reference and endpoint behavior.
A metric is a numeric time series.
Creating one? See Creating a Metric for which endpoints to call first for each kind of source.
Full field reference (types, enums, required/read-only flags) is generated from components.schemas.Metric in the API Reference tab, and tabulated field by field — with what each one's read-only status means for a write — in Field Reference. Or fetch the live OpenAPI document directly, see Getting Started for the URL.
GET /metrics · GET /metrics/:metricId
GET /metrics · GET /metrics/:metricIdReturns an array (list) or a single metric.
{
"id": "a5b92ef1de2b4a78b1fbab5ee0340113",
"name": "Monthly Recurring Revenue",
"description": "MRR across all plans",
"sourceId": "3880a65f8cdd4603b9ebb68a1edf03ef",
"sourceType": "DATAFEED",
"positiveDirection": "UP",
"numeric": true,
"locked": false,
"timeZone": "America/Toronto",
"status": "OK",
"timeRangeStart": "2025-01-01T00:00:00.000Z",
"timeRangeEnd": "2026-05-28T23:59:59.999Z",
"createdAt": "2025-01-05T14:12:00.000Z",
"updatedAt": "2026-05-20T09:30:00.000Z",
"properties": {
"numberFormat": "CURRENCY",
"currencyCode": "USD",
"aggregation": "SUM",
"dataShape": "SNAPSHOT"
},
"dimensions": [
{ "id": "4cec9274f2e645cf9f7a39b027c63df4", "name": "Plan", "fieldId": "4274514cd27b485e8d4630dd611c01e5", "dataType": "STRING" }
],
"version": "1.0"
}A metric sourced from a dbt connection ("sourceType": "DBT") additionally carries the semantic layer's own description of it under properties. All four are read-only:
| Field | Notes |
|---|---|
properties.slMetricType | the dbt metric type, e.g. SIMPLE, RATIO, CUMULATIVE |
properties.supportedPeriodicities | comma-separated periodicities the metric can be queried at, e.g. 1d,1w,1mo |
properties.slForceGroupOnTime | cumulative grain-to-date metrics only. The dbt grain the metric must be grouped by, which fixes the periodicity it is queried at: DAY, WEEK, MONTH, QUARTER or YEAR |
properties.slForceGroupOnTimeAggregation | cumulative grain-to-date metrics only. Which value in each of those periods represents it: FIRST or LAST |
The last two appear only on a cumulative metric that dbt declares as grain-to-date; they are absent otherwise.
They follow the general read-only rule (see Getting Started). POST /metrics ignores them outright, so a dbt metric listed by GET /direct_connections/:connectionId/metrics can be posted back exactly as returned. PUT /metrics/:metricId ignores them while they match the stored metric — so a GET → edit → PUT round trip needs no fields dropped first — and rejects a changed one with 400. Unlike status and the data timestamps, these are not exempt from that comparison: they describe the dbt definition, so a differing value means the caller is trying to redefine the upstream metric through this API. If the dbt project itself changed, re-read the metric before writing it.
dbt metrics also carry an internal slMetricDigest upstream, used to detect that the dbt definition changed. It is not part of the public spec — it is never returned, and cannot be set. POST /metrics ignores it if sent, so a spec captured from an older response still posts back cleanly.
Discover the dbt metrics available to create with GET /direct_connections/:connectionId/metrics.
POST /metrics
POST /metricsSend a metric spec. Returns 201 with the meta/data envelope described in Getting Started. Read-only fields need not be stripped out — on create they are simply ignored.
name is required on every metric, calculated ones included — a nameless metric is rejected with 400. So is a dimension without a name: a dimension is identified by its name, so dimensions[].name is required whenever you send dimensions at all.
Every metric names the source its data comes from, so sourceId is required — a metric without one could never return data. Which id to send depends on the source type; the guide for your source describes it.
The one exception is a calculated metric, which has no source of its own: it derives its values from other metrics through formula. Sending a non-empty formula is what makes a request calculated, and a stored calculated metric reports sourceType: "CALCULATED" — either one stands in for sourceId. Any other write without a sourceId is rejected with 400.
A create that sends no properties.numberFormat — including one that omits properties altogether — is stored with "numberFormat": "NUMERIC", a plain unformatted number.
A DATAFEED- or SQL-sourced metric reads a raw column, so it must say how to roll those values up. A create that sends no properties.aggregation is stored with "SUM", or "COUNT" when the metric declares "numeric": false. The other source types carry the answer themselves — a calculated metric aggregates through its formula, a DBT or CUBE metric inherits it upstream — so nothing is defaulted for them.
A DATAFEED-sourced metric's dimensions are always strings — a feed's columns are read as strings whatever they were generated from — so dimensions[].dataType may be omitted on a create and is stored as "STRING". A SQL- or CUBE-sourced metric's dimensions carry the connection's real column type, so send it there.
PUT /metrics/:metricId defaults none of the three: an omitted editable field is cleared there, as everywhere else.
PUT /metrics/:metricId
PUT /metrics/:metricIdReplace the editable fields of a metric. Read-only fields must not change (400 otherwise). Returns the updated metric.
An omitted editable field is cleared, so resend sourceId — the same requirement as POST /metrics above applies here, and an update that would leave a non-calculated metric with no source is rejected with 400.
DELETE /metrics/:metricId
DELETE /metrics/:metricId204 No Content.
POST /metrics/:metricId/query
POST /metrics/:metricId/queryQuery a metric's data over a time range.
Request body (recognised fields: periodicity, groupby, aggregation, range, tz, filter, members_only — other fields are ignored):
{
"periodicity": "1M",
"range": ["2025-12-01T00:00:00.000Z", "2026-05-28T23:59:59.999Z"]
}Filtering by dimension
filter narrows the rows the query reads. It is an object keyed by dimension name — use the metric's own dimensions[].name — and each dimension takes one of two filter shapes.
A member filter keeps only the members you list:
{
"filter": {
"Country": { "type": "member", "members": ["Canada", "Mexico"], "inverted": false }
}
}That query returns only the Canada and Mexico members of the Country dimension. Setting "inverted": true flips it: every member except Canada and Mexico.
A conditional filter matches members by their text instead of naming them. It wraps one or more conditions in a multi filter:
{
"filter": {
"EventName": {
"type": "multi",
"filters": [
{ "type": "text_condition", "op": "starts_with", "inverted": false, "value": "f" }
]
}
}
}That returns every EventName member starting with f. The available op values are contains, starts_with and ends_with, and inverted negates the condition the same way it does on a member filter.
Filtering several dimensions at once is a matter of adding more keys to filter; each dimension is narrowed independently.
Response — columns + row data + metadata (the time column is ISO):
{
"columns": ["time", "value"],
"data": [
["2025-12-01T00:00:00.000Z", 42100],
["2026-01-01T00:00:00.000Z", 43850]
],
"metadata": {
"rowCount": 2,
"lastDataUpdate": "2026-05-28T06:00:00.000Z",
"query": { "periodicity": "1M", "range": ["2025-12-01T00:00:00.000Z", "2026-05-28T23:59:59.999Z"] }
}
}Updated 6 days ago