Query
query is the unified Costory explorer for cost, usage, saved metrics, external metrics, formulas, and budgets. Supporting tools discover dimensions, pick a groupBy, resolve metric IDs, and suggest next steps — call them before guessing field names.
Load this skill first for investigation, breakdowns, comparisons, unit economics (cost per X), or budget burn analysis.
When to Trigger
- Exploring or breaking down cloud costs (by service, region, team, environment, …)
- Explorer-style period comparison (PoP totals / composition) — not the “why did spend move?” change-tree explanation
- Cost + usage or cost + business metric (unit economics / formulas)
- Budget vs spend, including month-to-date rolling burn and cumulated budget vs cumulated cost
- K8s waste vs K8s cost (usage metrics + optional waste ratio)
- Effective savings (
contracted_costminuseffective_cost) - Validating CEL filters before building a dashboard or report
- User asks "what are we spending on X?"
- After a DIGEST explain preview: drill into a user-named node with
filterCel
Hand off instead of this skill: "why did the bill jump?" / "what changed last month?" / "explain the spike" → load recipes → explain-period-change (or reports Explain). First answer is preview_report_widget DIGEST, not query + compare.
Core concepts
SCOPE vs SPLIT
| Idea | Field | Meaning | Example |
|---|---|---|---|
| SPLIT | groupBy |
How to break the result into series/rows | "Cost per environment" → groupBy: "cos_environment", no filter |
| SCOPE | filterCel |
Which rows to include (CEL) | "EC2 costs" → filterCel: "cos_service_name in [\"AmazonEC2\"]", groupBy something else |
| Combined | both | Scoped breakdown | "EC2 per region" → filter EC2 + groupBy: "cos_region" |
Never put the split dimension into filterCel (or the filter into groupBy). Discover exact CEL names with search (type: ["dimensions"]) — Costory labels use a cos_ prefix (e.g. cos_service_name).
Query naming
name— single lowercase letter (a–z) for formula referencesalias— human label, max 50 characters (e.g."Cost by environment")- Never put descriptive text in
name
Period and aggregation
Provide a period with either datePreset or from + to (ISO dates, inclusive). Do not combine datePreset with explicit dates.
| Mode | When |
|---|---|
datePreset |
Preferred whenever a value from the DatePreset enum below matches the requested range |
from + to |
Custom absolute ranges, and any relative range no preset covers — do not invent a preset token |
DatePreset enum — these 16 values are the only accepted tokens. The server validates datePreset against this exact set and rejects anything else with MCP error -32602: Invalid option … path: ["datePreset"]. Do not guess or extrapolate a token (e.g. there is no LAST_2_MONTHS, TRAILING_60_DAYS, or WTD) — if none of these matches, fall back to explicit from/to.
| Group | Values |
|---|---|
| Trailing (rolling window ending today) | TRAILING_3_DAYS, TRAILING_7_DAYS, TRAILING_30_DAYS, TRAILING_45_DAYS, TRAILING_90_DAYS, TRAILING_14_WEEKS |
| Last complete period | LAST_WEEK, LAST_MONTH, LAST_3_MONTHS, LAST_6_MONTHS, LAST_12_MONTHS, LAST_4_YEARS, LAST_INVOICE_MONTH |
| To-date (period start → today) | MTD, QTD, YTD |
aggBy |
Use when |
|---|---|
Day / Week / Month |
Evolution / trends within the range |
Period |
Single-bucket total or composition for the whole range (good with groupBy) |
Hour |
Rare; fine-grained short windows |
Billing lag: AWS/GCP cost data can take up to 48 hours to land. Presets are already shifted back by two days so they do not include incomplete days. Do not treat yesterday or today as complete; those days may appear with partial figures. If the user asks for a calendar window that includes them, use explicit from/to and say the last 1–2 days can be incomplete.
Time series vs comparison: omit compare for evolution within one range. For period-over-period deltas, pass compare — prefer datePreset on the primary period + compare: {} (or { enabled: true }) so the server auto-derives the preceding period (preset-aware, e.g. LAST_MONTH → previous calendar month). Pass compare: { from, to } only for a custom other range. Optional compare.chartType: WATERFALL (default), TABLE, or KPI_BREAKDOWN.
Limit
Omit limit unless needed (default 100 groups). Raise up to 1000 only for long-tail / full breakdown lists.
Supporting tools
| When | Tool | Notes |
|---|---|---|
| Start of conversation / org context | get_context |
Popular groupBys, recent dashboards, external integrations |
| Discover CEL field names & values | search type: ["dimensions"] |
query: "" lists all dimensions; keyword narrows values |
| Find dashboards, budgets, VDIMs, events | search (keyword) |
Short terms ("kubernetes", not full sentences) |
| Unsure which axis to split by | suggest_groupby |
Pass the planned period (from/to) + filterCel |
| Saved business metrics | list_metrics |
Use returned id as metricId for type: "metric" |
| Live Tsuga / BigQuery metrics | list_metrics includeExternal: true + search |
Never call includeExternal without a search term |
| Infra usage units (CPU hours, …) | suggest_usage_metrics |
Needs a specific filterCel scope |
| Budget version ID | search → get |
search returns parent budget id; get yields budgetVersionId for type: "budget" |
| Correlate spikes | list_events |
Same date range as the query |
| Offer next steps | suggest_actions |
After query / get; set hasEvents / hasDiff |
If slug auto-detect fails, call list_organizations and pass slug.
Query types
type |
Required extras | Discover via |
|---|---|---|
cost |
metricId (default "cost"), currency (default "USD") |
— |
usage |
metricId |
suggest_usage_metrics |
metric |
metricId |
list_metrics |
externalMetric |
integrationId, metricName, aggregator; BigQuery also needs dateColumn, metricColumn, gapFillingMethod |
list_metrics includeExternal: true |
formula |
formula referencing letters ("a / b") |
— |
budget |
budgetId = budget version id (not parent id from search) |
search → get |
Optional per series: chartType (BAR | LINE | AREA | WATERFALL | TABLE, default LINE), groupBy, filterCel (cost/usage), alias, rollingAggregation.
chartTypeaccepts only those five values — any other value is rejected with a zod validation error. There is noDONUTenum: for a single-period composition usechartType: "BAR"+aggBy: "Period"(and agroupBy).KPI_BREAKDOWNis compare-only — it is valid on the dashboardscompare.chartType, never on a serieschartType.
Cost columns (metricId): cost, effective_cost, list_cost, contracted_cost, unblended_cost, net_unblended_cost, amortized_cost, net_amortized_cost.
Null labels: unlabelled resources are CEL null — use == null / != null, not is_null or the string "null".
Virtual dimensions: use immutable bqName from list/get VDIM tools as groupBy / filterCel (not display name). After publish, wait until computeStatus is COMPLETED.
External metrics: prefer saved { type: "metric" } when one exists. Tsuga: metricName = provider metric; groupByFields = attributes; conditions = optional provider filter. BigQuery: provider: "bigquery", metricName = project.dataset.table.
Workflow A — Standard cost investigation
get_skillwithskillId: "query"(this guide)get_context- Resolve dimensions:
searchwithtype: ["dimensions"](empty or keyword) - If the split axis is unclear →
suggest_groupbywith the planned period +filterCel querywithtype: "cost", correctgroupBy/filterCel, period (datePresetpreferred),aggBy- Optional:
list_eventsfor the same range; thensuggest_actions
Example — total costs last month:
{
"queries": [{ "type": "cost", "name": "a", "metricId": "cost", "currency": "USD" }],
"datePreset": "LAST_MONTH",
"aggBy": "Month"
}
Example — AWS by service (trailing 90 days composition):
{
"queries": [{
"type": "cost",
"name": "a",
"alias": "AWS by service",
"metricId": "cost",
"currency": "USD",
"groupBy": "cos_service_name",
"filterCel": "cos_provider in [\"AWS\"]",
"chartType": "BAR"
}],
"datePreset": "TRAILING_90_DAYS",
"aggBy": "Period"
}
Example — resources without an environment label:
{
"queries": [{
"type": "cost",
"name": "a",
"metricId": "cost",
"currency": "USD",
"filterCel": "cos_environment == null"
}],
"datePreset": "TRAILING_30_DAYS",
"aggBy": "Month"
}
Workflow B — Explorer period comparison (PoP totals)
Not for change-tree explanation. If the user wants why spend moved / explain last month / what drove the delta as a drivers tree → stop. Load recipes → explain-period-change (or reports Explain) and call preview_report_widget DIGEST. Do not use this workflow as a substitute.
Use this workflow only for explorer-style period-over-period numbers (totals, composition, a single groupBy table) when DIGEST is not the ask.
- Same discovery as Workflow A
querywith a primary period andcompare:- Prefer
datePreset+compare: {}(or{ enabled: true }) to auto-derive the preceding period - Use
compare: { from, to }only when the other range is a custom window
- Prefer
- Optional:
list_events+suggest_actionswithhasDiff: true
Example — last month vs the preceding period (auto-derived):
{
"queries": [{ "type": "cost", "name": "a", "metricId": "cost", "currency": "USD" }],
"datePreset": "LAST_MONTH",
"compare": {}
}
Example — custom comparison window (explicit dates on both sides):
{
"queries": [{ "type": "cost", "name": "a", "metricId": "cost", "currency": "USD" }],
"from": "2026-07-01",
"to": "2026-07-17",
"compare": { "from": "2026-06-01", "to": "2026-06-30" }
}
Add groupBy (e.g. cos_service_name) when you need a flat explorer breakdown — not a DIGEST hierarchy.
Workflow C — Pick a groupBy when stuck
- Define scope (
filterCel) and period suggest_groupbywith thatfrom/to/filterCelqueryusing the suggested dimension asgroupBy
Example — EC2 spiked; what to investigate?
// suggest_groupby
{ "from": "2026-06-01", "to": "2026-06-30", "filterCel": "cos_service_name in [\"AmazonEC2\"]" }
// then query with the suggested groupBy
{
"queries": [{
"type": "cost",
"name": "a",
"metricId": "cost",
"currency": "USD",
"filterCel": "cos_service_name in [\"AmazonEC2\"]",
"groupBy": "<suggested-dimension>",
"chartType": "BAR"
}],
"datePreset": "LAST_MONTH",
"aggBy": "Day"
}
Workflow D — Usage alongside cost
- Narrow scope with
filterCel(required for useful suggestions) suggest_usage_metricswith thatfilterCelquerywith cost (a) + usage (b)
{
"queries": [
{ "type": "cost", "name": "a", "metricId": "cost", "currency": "USD" },
{ "type": "usage", "name": "b", "metricId": "k8s_cpu_hours", "alias": "CPU hours" }
],
"datePreset": "LAST_MONTH",
"aggBy": "Week"
}
Workflow E — Unit economics (cost per metric)
Saved metric:
list_metrics→ pickidquerycost + metric + formula
{
"queries": [
{ "type": "cost", "name": "a", "metricId": "cost", "currency": "USD" },
{ "type": "metric", "name": "b", "metricId": "<metric-id>" },
{ "type": "formula", "name": "c", "alias": "Cost per unit", "formula": "a / b" }
],
"datePreset": "TRAILING_30_DAYS",
"aggBy": "Week"
}
Live external (Tsuga) — after list_metrics with includeExternal: true, search: "request":
{
"queries": [
{ "type": "cost", "name": "a", "metricId": "cost", "currency": "USD" },
{
"type": "externalMetric",
"name": "b",
"provider": "tsuga",
"integrationId": "<integration-id>",
"metricName": "<metric-name>",
"aggregator": "SUM"
},
{ "type": "formula", "name": "c", "formula": "a / b" }
],
"datePreset": "TRAILING_30_DAYS",
"aggBy": "Week"
}
BigQuery revenue table — after list_metrics with includeExternal: true, search: "revenue":
{
"queries": [
{ "type": "cost", "name": "a", "metricId": "cost", "currency": "USD" },
{
"type": "externalMetric",
"name": "b",
"provider": "bigquery",
"integrationId": "<integration-id>",
"metricName": "my-project.analytics.revenue",
"dateColumn": "event_date",
"metricColumn": "amount",
"gapFillingMethod": "ZERO",
"aggregator": "SUM"
},
{ "type": "formula", "name": "c", "formula": "a / b" }
],
"datePreset": "TRAILING_30_DAYS",
"aggBy": "Week"
}
Workflow F — Budgets
searchwithtype: ["budgets"](or keyword) → parent budgetidgetwith that id → readbudgetVersionId(and aligncostMetricId/currencywhen present)querywithtype: "budget"andbudgetId: "<budgetVersionId>"
Monthly budget line:
{
"queries": [{ "type": "budget", "name": "a", "budgetId": "<budgetVersionId>" }],
"datePreset": "YTD",
"aggBy": "Month"
}
Month-to-date burn (which day did we hit the budget?) — use datePreset: "MTD" for the current month (LAST_MONTH for the previous one):
{
"queries": [{
"type": "budget",
"name": "a",
"budgetId": "<budgetVersionId>",
"rollingAggregation": { "aggregator": "SUM", "window": { "preset": "MONTH" } }
}],
"datePreset": "MTD",
"aggBy": "Day"
}
MTD cost vs MTD budget utilization:
{
"queries": [
{
"type": "cost",
"name": "a",
"metricId": "cost",
"currency": "USD",
"rollingAggregation": { "aggregator": "SUM", "window": { "preset": "MONTH" } }
},
{
"type": "budget",
"name": "b",
"budgetId": "<budgetVersionId>",
"rollingAggregation": { "aggregator": "SUM", "window": { "preset": "MONTH" } }
},
{ "type": "formula", "name": "c", "alias": "Budget utilization", "formula": "a / b" }
],
"datePreset": "MTD",
"aggBy": "Day"
}
Cumulated budget vs cumulated cost (same rolling window, no ratio — plot both series):
Use rollingAggregation on both legs with the same window preset so each day shows a running total within the month (or use aggBy: "Month" without rolling for calendar-month buckets). Resolve budgetVersionId via search → get; align metricId / currency with the budget when get returns them.
{
"queries": [
{
"type": "cost",
"name": "a",
"alias": "Cumulated cost",
"metricId": "cost",
"currency": "USD",
"rollingAggregation": { "aggregator": "SUM", "window": { "preset": "MONTH" } }
},
{
"type": "budget",
"name": "b",
"alias": "Cumulated budget",
"budgetId": "<budgetVersionId>",
"rollingAggregation": { "aggregator": "SUM", "window": { "preset": "MONTH" } }
}
],
"datePreset": "YTD",
"aggBy": "Day"
}
Workflow G — K8s waste vs K8s cost
- Scope to clusters:
filterCel: "cos_cluster_name != null"(or a specific cluster in CEL) suggest_usage_metricswith thatfilterCel→ confirmk8s_costandk8s_waste(or org-specific IDs returned by the tool)querywaste, cost, and optionally a waste ratio
Example — waste vs allocated K8s cost + waste ratio:
{
"queries": [
{
"type": "usage",
"name": "a",
"alias": "K8s cost",
"metricId": "k8s_cost",
"filterCel": "cos_cluster_name != null"
},
{
"type": "usage",
"name": "b",
"alias": "K8s waste",
"metricId": "k8s_waste",
"filterCel": "cos_cluster_name != null"
},
{
"type": "formula",
"name": "c",
"alias": "Waste ratio",
"formula": "b / a"
}
],
"datePreset": "TRAILING_30_DAYS",
"aggBy": "Week"
}
Add groupBy (e.g. cos_cluster_name or cos_namespace_reallocated) when the user wants waste drivers, not just totals.
Workflow H — Effective savings (contracted vs effective)
Effective savings ≈ on-demand / contracted spend minus what you actually pay after commitments (Savings Plans, RIs, CUDs):
contracted_cost − effective_cost
Use the same filterCel / groupBy on every leg so scope and split stay aligned.
Example — total effective savings last month:
{
"queries": [
{
"type": "cost",
"name": "a",
"alias": "Contracted (without savings)",
"metricId": "contracted_cost",
"currency": "USD"
},
{
"type": "cost",
"name": "b",
"alias": "Effective cost",
"metricId": "effective_cost",
"currency": "USD"
},
{
"type": "formula",
"name": "c",
"alias": "Effective savings",
"formula": "a - b"
}
],
"datePreset": "LAST_MONTH",
"aggBy": "Month"
}
Example — savings by service (composition for the range):
{
"queries": [
{
"type": "cost",
"name": "a",
"alias": "Contracted",
"metricId": "contracted_cost",
"currency": "USD",
"groupBy": "cos_service_name",
"chartType": "BAR"
},
{
"type": "cost",
"name": "b",
"alias": "Effective",
"metricId": "effective_cost",
"currency": "USD",
"groupBy": "cos_service_name"
},
{
"type": "formula",
"name": "c",
"alias": "Effective savings",
"formula": "a - b",
"chartType": "BAR"
}
],
"datePreset": "LAST_MONTH",
"aggBy": "Period"
}
For credit/discount runway reporting (charge-category trend), use the recipes skill → provider-credits instead.
Workflow I — From search hit to query
search hit |
Next step |
|---|---|
| Dimension value | query with that field in groupBy or filterCel |
| Dashboard | get (or get_skill dashboards + update_dashboard if extending) |
| Budget | get → budgetVersionId → query type: "budget" |
| Report | Open/use the report URL; for DIGEST work load reports skill |
| Virtual dimension | Use bqName in groupBy/filterCel; load virtual-dimensions if editing |
Post-query
After useful results, consider:
list_eventsfor the resolved date range (explain spikes)suggest_actions—{ "hasEvents": true|false, "hasDiff": true|false }- Hand off:
dashboardsto persist the view; for a change-tree explanation userecipes→explain-period-change(not “schedule a DIGEST” as a substitute for preview);virtual-dimensionsif the needed axis does not exist
Safety / anti-patterns
- Prefer
datePresetover hand-computedfrom/towhen a preset matches — do not freeze last-month/trailing windows as absolute dates - Do not treat yesterday / today as complete billing days — presets are already shifted ~48h; do not invent a “correction” on top
- Do not invent a
datePresettoken — only the 16 enum values above are valid; if none matches the requested range, use explicitfrom/to(a bad token fails with-32602 Invalid option … path: ["datePreset"]) - Do not combine
datePresetwithfrom/to - Do not invent CEL field names — use
searchtype: ["dimensions"]orget_contextpopular groupBys - Do not put descriptive labels in
name— usealias - Do not exceed 50 characters in
alias— longer labels fail with-32602 too_big … maximum: 50 … path: [..., "alias"]; keep series labels short - Do not confuse SCOPE (
filterCel) with SPLIT (groupBy) - Do not pass the parent budget id from
searchasbudgetId— resolvebudgetVersionIdviaget - Do not call
suggest_usage_metricswithout a specificfilterCel - Do not call
list_metricswithincludeExternal: truewithout asearchterm - Do not set
limitby default — only when >100 groups are needed - Do not use string
"null"for missing labels — use CEL== null - Prefer saved
type: "metric"overexternalMetricwhen a saved metric already exists - Do not answer "explain / what changed last month" with
query+compare— hand off to DIGEST preview (explain-period-change/reportsExplain)
Related Skills / Next Steps
dashboards— persist a validated query as widgets (skillId: "dashboards")reports— Explain (DIGEST preview) or scheduled delivery (skillId: "reports")recipes→explain-period-change— one-shot spend-change tree (prefer over Workflow B)virtual-dimensions— custom cost axis when no dimension fits (skillId: "virtual-dimensions")recipes— reallocate shared cost by an external / usage metric →reallocate-by-external-metricbigquery— BigQuery SKU families, physical vs logical, slots vs load jobs, labels (skillId: "bigquery")