Agents now connect to datahashi over OAuth — scoped tokens, no API keys to paste
datahashi
Open console

Semantic Query

Ask for the number. We write the SQL.

A Semantic Query is a structured object — metrics × dimensions × filters. BI tools, apps, and MCP agents send the same shape; datahashi compiles fan-out-safe SQL and runs it in your warehouse. Callers never invent joins.

Input and output

semantic-query.json
{
  "metrics": ["avg_order_value"],
  "dimensions": [
    "Customer.region",
    { "name": "ordered_at", "entity": "Order",
      "grain": "month" }
  ],
  "filters": [
    { "dimension": "status", "entity": "Order",
      "op": "eq", "value": "completed" }
  ],
  "order": [{ "ref": "ordered_at" }],
  "limit": 100
}
compiled.sql
SELECT c.region,
       date_trunc('month', o.ordered_at) AS ordered_at,
       SUM(o.amount) / COUNT(o.id)       AS avg_order_value
FROM public.orders o
JOIN public.customers c ON o.customer_id = c.id
WHERE o.status = 'completed'
GROUP BY c.region, ordered_at
ORDER BY ordered_at
LIMIT 100;
Why this shape: every consumer — human or agent — speaks the same object language. The caller knows nothing about join paths, double-counting, or which warehouse backs the model. datahashi handles all three.

The object

Request fields

metrics
Governed metric names, e.g. "avg_order_value". Available in both modes.
measures
Raw measure names, "gmv" or "Order.gmv". Explore mode only.
dimensions
Group-by. Each is "Entity.name" or { name, entity, grain } for a time bucket.
filters
{ dimension, op, value } — op is eq, ne, lt, le, gt, ge, in, not_in, contains, starts_with, ends_with, is_null. Never raw SQL.
order
[{ ref, desc }] over the names you selected.
view
Restrict the query to a curated view of the model.
limit
Row cap. Also bounded by the workspace's cost policy.
version
Pin a published model version. Defaults to the current one.

What you can rely on

Joins found for you

Ask for a measure on one entity and a dimension on another; the path between them is worked out. Callers — BI tools, apps, and MCP agents — never invent joins.

No double-counting

A measure joined across a one-to-many relationship isn't inflated. Fan-out-safe SQL runs in your warehouse — counts of distinct things stay correct.

Averages that match finance

avg_order_value is revenue / order_count on the aggregated rows — never avg(amount) row by row, which weights big orders wrong.

The request never changes

BI, apps, and MCP agents send the same structured object. It compiles to Snowflake, BigQuery, Postgres, MySQL, or DuckDB SQL. Change warehouses and nothing that queries you has to be rewritten.

Errors

Stable codes, JSON envelope

Every query error is { "error": { "code", "message" } } with a stable code you can branch on.

HTTP Code When
400 bad_request invalid grain, operator, type mismatch, ambiguity
401 unauthenticated missing or invalid bearer token
403 forbidden a governed credential asked for raw measures
404 not_found unknown metric, measure, or dimension
429 over_budget the query exceeds the workspace cost policy
503 overloaded the service is at capacity

Every consumer sends this shape — humans and agents alike.