Semantic query syntax
JSON for Semantic Queries, filter operators, having clauses, and pagination.
Updated
A Semantic Query is the structured request accepted by POST /v1/query and the MCP tool run_semantic_query.
The body never names a model and never names a tenant. Pin a published model with version. Omit version for the current model.
Top-level query object
{
"version": "current",
"metrics": ["avg_order_value"],
"measures": ["Order.revenue"],
"dimensions": [
"Customer.region",
{ "name": "order_date", "entity": "Order", "grain": "month" }
],
"filters": [],
"having": [],
"segments": ["completed_orders"],
"view": "finance_overview",
"saved_query": "revenue_by_region",
"order": [
{ "ref": "revenue", "desc": true }
],
"limit": 100,
"offset": 0,
"time_zone": "Asia/Tokyo",
"percent_of_total": ["revenue"],
"fill_gaps": false,
"include_sql": false
}
Field definitions
| Field | Type | Description |
|---|---|---|
version |
string | Published model version to pin. Omit for the current model. |
metrics |
string[] | Governed metric names. |
measures |
string[] or object[] | Raw measures (Order.revenue or {name, entity}). Refused when the caller’s grants cannot select raw measures. |
dimensions |
array | A string (region or Order.region) or {name, entity, grain, relationship}. |
filters |
array | Pre-aggregation conditions. Combined with AND. |
filter_group |
object | Nested AND / OR. See below. |
having |
array | Conditions on selected aggregated columns. |
segments |
string[] | Model segments. Expanded before row-level security. Combined with filters by AND. |
view |
string | Named view. Its filters are injected. Its surface is the ceiling. |
saved_query |
string | Stored query used as the base. Sent filters and segments are added. Any other list you send replaces the stored list. |
order |
array | {ref, desc}. ref is a selected output. desc: true sorts descending. |
limit |
integer | Maximum rows. Capped by ENGINE_MAX_ROWS. |
offset |
integer | Row offset. Needs a positive limit and a non-empty order. |
time_zone |
string | IANA zone for day through year buckets of timestamp dimensions. Omit for UTC. A bad zone is 400 invalid_time_zone. A rollup never answers a non-UTC bucketed query. |
percent_of_total |
string[] | Selected additive measures returned again as a 0–1 share, in column <name>_share_of_total. |
fill_gaps |
boolean | Emit a row for every empty time bucket. Needs exactly one grouped calendar-grain dimension, no limit or offset, and an order on that dimension only. |
include_sql |
boolean | Return compiled SQL. Honoured only when ENGINE_ALLOW_SQL=true. |
include_resolved is not a REST field. The MCP tool run_semantic_query accepts it. When it is true, the tool adds the normalized query that ran. That echo never includes a server-injected row-level-security predicate.
Dimension specifications
A dimension is a string or an object:
[
"Customer.region",
{
"name": "order_date",
"entity": "Order",
"grain": "quarter"
}
]
grain:minute,hour,day,week,month,quarter,fiscal_quarter,year, orfiscal_year. It must be one of the dimension’s declared grains.entity: Required when the bare name exists on more than one entity. Otherwise the error reason isambiguous_dimensionorambiguous_measure.relationship: Names the join edge when two entities have more than one relationship.
Do not put Entity.name inside the object name field. A string "Order.region" is split for you. An object keeps name and entity separate.
Filter operators
Single condition
{
"dimension": "status",
"entity": "Order",
"op": "eq",
"value": "completed"
}
Supported operators
| Operator | Expected value |
|---|---|
eq, ne, lt, le, gt, ge |
Scalar |
in, not_in |
Non-empty array |
between |
Two-element array [low, high], inclusive |
contains, starts_with, ends_with, not_contains, not_starts_with, not_ends_with |
String. String dimensions only. |
is_null, is_not_null |
null |
The query API also accepts aliases such as >=, gte, ==, !=, <>, >, <, <=, and lte. Model YAML does not. Use the canonical names in YAML.
The engine binds each value as a parameter. It does not paste the value into SQL text.
Filter groups
filters is a flat list combined with AND. Nested logic is a sibling field, filter_group:
{
"filters": [],
"filter_group": {
"op": "or",
"filters": [
{ "dimension": "region", "op": "eq", "value": "emea" }
],
"groups": [
{
"op": "and",
"filters": [
{ "dimension": "region", "op": "eq", "value": "na" },
{ "dimension": "plan_tier", "op": "eq", "value": "enterprise" }
]
}
]
}
}
op is and or or. An empty op means and. Do not wrap conditions in { "or": [ ... ] } inside filters.
Having clauses
having filters run after aggregation. ref names a selected metric or measure, never a dimension.
{
"having": [
{ "ref": "revenue", "op": "gt", "value": 50000 },
{ "ref": "order_count", "op": "ge", "value": 10 }
]
}
having is not valid on a rolling-window measure.
Drill-down request
Used by POST /v1/drill-down and the MCP drill_down tool. measure is a raw measure, never a metric.
{
"measure": "Order.revenue",
"cell": [
{ "dimension": "region", "entity": "Customer", "value": "apac" },
{ "dimension": "order_date", "entity": "Order", "grain": "month", "value": "2026-03-01" }
],
"limit": 50
}
Query refinement
MCP refine_semantic_query changes an existing query and runs the merged query.
{
"base": {
"measures": ["Order.revenue"],
"dimensions": ["Customer.region"]
},
"add_dimensions": [{ "name": "status", "entity": "Order" }],
"add_filters": [{ "dimension": "region", "entity": "Customer", "op": "eq", "value": "na" }],
"set_limit": 25
}
add_dimensions and add_filters append. set_metrics, set_measures, set_order, set_limit, and set_offset replace. drop_dimensions and drop_filters remove entries by dimension name. The tool always returns the resolved query.
Next: Environment variables · REST API.