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

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

FieldNotes
properties.slMetricTypethe dbt metric type, e.g. SIMPLE, RATIO, CUMULATIVE
properties.supportedPeriodicitiescomma-separated periodicities the metric can be queried at, e.g. 1d,1w,1mo
properties.slForceGroupOnTimecumulative 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.slForceGroupOnTimeAggregationcumulative 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

Send 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

Replace 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

204 No Content.

POST /metrics/:metricId/query

Query 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"] }
  }
}

Did this page help you?