02Agents & API
Ask for the metric. We write the SQL.
Send metric names, dimensions and filters over REST or MCP. The rows you get back match every other tool.
{
"metrics": ["avg_order_value"],
"dimensions": [
"Customer.region",
{ "name": "ordered_at",
"entity": "Order",
"grain": "month" }
],
"filters": [
{ "dimension": "status",
"entity": "Order",
"op": "eq",
"value": "completed" }
],
"limit": 100
}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 1, 2
LIMIT 100;| region | ordered_at | avg_order_value |
|---|---|---|
| EMEA | 2026-01-01 | 118.40 |
| EMEA | 2026-02-01 | 121.05 |
| NA | 2026-01-01 | 132.90 |
| NA | 2026-02-01 | 130.15 |
One request, the SQL it becomes, and the rows that come back.
Who asks
Agents, apps and BI send the same request
One JSON shape, whoever is asking. Agents also get a catalog to choose from.
- Agents, over MCP
- They sign in with OAuth, see only the metrics they may use and ask for them by name. They can't invent a join or read a hidden row.
- Apps, over REST
- One endpoint. Apps ask for metrics by name, never for tables.
- BI and analysts
- The same metric names the agent uses, so the numbers match.
{
"name": "run_semantic_query",
"arguments": {
"metrics": ["revenue"],
"dimensions": [{
"name": "ordered_at",
"entity": "Order",
"grain": "quarter"
}]
}
}Reference
Eight fields in, six error codes out
Request fields
- metrics
- Governed metric names, like "avg_order_value".
- measures
- Raw measure names. Explore mode only.
- dimensions
- What to group by, including time buckets.
- filters
- Conditions like eq, gt, in or contains. Never raw SQL.
- order
- Sort by any field you selected.
- view
- Limit the query to a curated view.
- limit
- Row cap. Cost limits apply too.
- version
- Pin a model version. Defaults to the latest.
Error codes
| HTTP | Code | When |
|---|---|---|
| 400 | bad_request | invalid request |
| 401 | unauthenticated | missing or invalid token |
| 403 | forbidden | governed key asked for raw measures |
| 404 | not_found | unknown metric or dimension |
| 429 | over_budget | over the cost limit |
| 503 | overloaded | service at capacity |
Every error has one shape: { "error": { "code", "message" } }
What you can rely on
Same request, same answer
Joins found for you
Ask for a measure and a dimension. The model supplies the join.
Totals that don't inflate
One-to-many joins never count a row twice.
Averages that match finance
Computed from totals, never by averaging averages.
A request that outlives your warehouse
Switch warehouses and every caller keeps sending the same JSON.
Repeat questions skip the scan
A short-lived result cache answers a repeated question without touching the warehouse. Opt-in rollups do the same for your busiest ones.
One noisy caller can't slow the rest
A rate limiter answers with a clear 429 or 503 instead of making everyone wait.
We'll publish speed numbers once there's a benchmark. How the password stays in your network →
Send your first request
Follow the query guide, or open the console and create a key.