Azure Cost Analysis via Cost Management REST API
Query Azure costs programmatically using the Microsoft.CostManagement/query REST API through az rest.
Why az rest Instead of az costmanagement
The costmanagement CLI extension (v1.0.0) only exposes export and show-operation-result — no query subcommand. Use az rest to call the REST API directly.
Workflow
- Identify the target resource (name, resource group, full resource ID)
- Build a cost query JSON body
- Execute via
az rest - Parse and analyze results
API Endpoint
POST https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.CostManagement/query?api-version=2023-11-01
Scoping options:
- Subscription:
/subscriptions/{id}/providers/Microsoft.CostManagement/query - Resource group:
/subscriptions/{id}/resourceGroups/{rg}/providers/Microsoft.CostManagement/query
Query Body Structure
{
"type": "ActualCost",
"timeframe": "MonthToDate",
"dataset": {
"granularity": "Daily",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"grouping": [
{ "type": "Dimension", "name": "<DimensionName>" }
],
"filter": {
"dimensions": {
"name": "<DimensionName>",
"operator": "In",
"values": ["<value>"]
}
}
}
}
Key Parameters
| Field | Options |
|---|---|
type |
ActualCost, AmortizedCost |
timeframe |
MonthToDate, BillingMonthToDate, TheLastMonth, TheLastBillingMonth, WeekToDate, Custom |
granularity |
None, Daily, Monthly |
For Custom timeframe, add timePeriod:
"timeframe": "Custom",
"timePeriod": { "from": "2026-01-01T00:00:00Z", "to": "2026-01-31T23:59:59Z" }
Valid Dimensions
For grouping and filtering — use only these values:
ResourceGroup, ResourceGroupName, ResourceType, ResourceId, ResourceLocation, SubscriptionId, SubscriptionName, MeterCategory, MeterSubcategory, Meter, ServiceFamily, ServiceName, UnitOfMeasure, ChargeType, PublisherType, PricingModel, Frequency, BillingMonth, ReservationId, ReservationName, Product, PartNumber, ResourceGuid, BenefitId, BenefitName, Provider, InvoiceId, CostAllocationRuleName
Common pitfall: MeterName is invalid — use Meter instead.
Filter Constraints
ResourceIdonly supports theInoperator (notContains). Provide the full resource ID.- Other dimensions support
Inas well. - Combine filters with
and/or/not:
"filter": {
"and": [
{ "dimensions": { "name": "ResourceGroupName", "operator": "In", "values": ["my-rg"] } },
{ "dimensions": { "name": "MeterCategory", "operator": "In", "values": ["Virtual Machines"] } }
]
}
Discovering the Full Resource ID
az resource list --name <resource-name> --query "[0].id" -o tsv
Or filter by type:
az resource list --resource-type "Microsoft.Compute/virtualMachines" --query "[].{name:name, id:id}" -o table
Execution
Always write the query body to a file first (avoids shell escaping issues):
python3 -c '
import json
query = { ... } # build query dict
with open("/tmp/cost_query.json", "w") as f:
json.dump(query, f)
'
Then execute:
az rest --method post \
--url "https://management.azure.com/subscriptions/{sub-id}/providers/Microsoft.CostManagement/query?api-version=2023-11-01" \
--headers "Content-Type=application/json" \
--body @/tmp/cost_query.json \
-o json > /tmp/cost_result.json
Important: Always include --headers "Content-Type=application/json" when using --body @file to avoid 415 Unsupported Media Type errors.
Parsing Results
Response structure:
{
"properties": {
"columns": [ { "name": "Cost", "type": "Number" }, ... ],
"rows": [ [10.56, 20260201, "VM", "Virtual Machines", "USD"], ... ],
"nextLink": null
}
}
Parse with Python:
import json
with open("/tmp/cost_result.json") as f:
data = json.load(f)
rows = data["properties"]["rows"]
cols = [c["name"] for c in data["properties"]["columns"]]
for r in sorted(rows, key=lambda x: float(x[0]), reverse=True)[:10]:
print(f"${float(r[0]):.2f} — {r[1:]}")
Handle pagination if nextLink is not null by making a GET request to that URL.
Common Query Recipes
For specific resource, service, or subscription-level queries, see references/query-recipes.md.
Cost Reduction Analysis
After querying costs, apply these analysis steps:
- Identify top cost drivers — group by
ServiceNameorMeterCategoryat subscription scope - Drill into specific resources — filter by
ResourceIdwithDailygranularity to spot trends - Detect idle resources — look for flat daily costs with no active usage (always-on charges)
- Compare periods — run
TheLastMonthvsMonthToDateto find spikes - Check meter details — group by
Meter+MeterCategoryto understand what exactly is being charged