Reference: Cost & Commitments
Cost Explorer data, cost anomalies, and reservation and savings plan data. This is the data behind the Cost Explorer, Cost Anomaly Detection and Commitment Coverage pages. Every route here needs View Costs. Date range rules are in Conventions; Defender spend is on the Security page.
5 Routes
Cost Explorer
GET /api/cost-explorer-summary
Permission: View Costs
Cost per subscription, resource group and resource.
Parameters
| Name | Required | Notes |
|---|---|---|
startDate, endDate | no | YYYY-MM-DD; applied only when both are sent; one alone is ignored. Max 366 days |
tagFilters | no | URL-encoded JSON array of { "id": "<any>", "key": "<tag>", "value": "<value>", "operator": "equals" | "exists" | "missing" }. For exists/missing send "value": "". Invalid elements are dropped silently |
tagMode | no | and (default) or or |
locations | no | CSV of region names, exact and case-sensitive (e.g. eastus) |
includeLocations | no | 1 adds location to resource rows |
Errors
- 400Cost date errors
- 400
Invalid tagFilters format. Must be a JSON-encoded array. - 400
Invalid tagMode. Must be "and" or "or". - 500
Failed to retrieve cost explorer hierarchy summary
Response: { compactSummaries[], availableFilters: { resourceTypes[] }, totalCount }. Each summary:
| Field | Meaning |
|---|---|
entityId, displayName, entityType | subscription, resourceGroup, resource (cost rows at that level) |
subscriptionId, subscriptionName, resourceGroupName? | Location |
monthToDateCostTotal | Actual cost: the current month without dates, or the exact window with dates |
monthToDateAmortizedCostTotal? | Amortized; reservation-covered resources show non-zero here |
currency? | Currency of the row |
resourceTypeCounts | Per type |
historicalCounts | { historical, current }: cost for resources no longer in the inventory |
totalCount | Monthly cost documents for the row. Without dates the route loads the current and the previous month, so one resource can show totalCount: 2 with historicalCounts: { historical: 1, current: 1 } while monthToDateCostTotal still covers the current month only; with dates every row has totalCount: 1. The response-level totalCount is the sum of the row counts |
location? | With includeLocations=1 |
Notes
- Tag and location filters keep resource rows only.
- Don't sum
monthToDateCostTotalacross rows with differentcurrency.
Example
GET /api/cost-explorer-summary?startDate=2026-09-01&endDate=2026-09-30&tagFilters=%5B%7B%22id%22%3A%221%22%2C%22key%22%3A%22env%22%2C%22value%22%3A%22prod%22%2C%22operator%22%3A%22equals%22%7D%5DGET /api/cost-explorer-details
Permission: View Costs
Daily cost records for one entity. An out-of-scope subscription returns an empty result.
Parameters
| Name | Required | Notes |
|---|---|---|
entityType | yes | subscription, resourceGroup, resource |
subscriptionId | yes | Exact |
resourceGroupName, resourceName | per entityType | |
includeChildResources | no | true/false; subscription and resource group only |
startDate, endDate | no | Without dates, you get the current month plus the two before |
Errors
- 400
entityType and subscriptionId are required - 400
entityType must be one of subscription, resourceGroup, resource - 400
resourceGroupName is required for resourceGroup entityType - 400
resourceName and resourceGroupName are required for resource entityType - 400
includeChildResources must be true or false - 400Cost date errors
- 500
Failed to retrieve cost explorer details
Response: { entityType, entityId, costRecords[], totalCount }. Each record is one resource:
| Field | Meaning |
|---|---|
resourceId, originalResourceId, resourceName, resourceType, entityType | Identity |
subscriptionId, subscriptionName, resourceGroupName | Location |
isHistorical, month | Whether the resource has left the inventory, and the month |
recentDailyCosts[] | Oldest first: { date, cost, amortizedCost?, currency, meterBreakdown?[] }. Each meter entry: { meterCategory, meterSubCategory?, meter?, cost, amortizedCost?, pricingModel?: OnDemand | Reservation | SavingsPlan | Spot, benefitName?, chargeType?, publisherType? }. A negative cost is a refund |
costSnapshot | { monthToDateCost, monthTotalCost, averageDailyCost } |
computedMetrics.previousMonthCost | The calendar month before today, not the month before the requested range. 0 when that month is not among the loaded months |
costCollectionMetadata | { month, monthStartDate, monthEndDate, daysWithCostData, isFinalized } |
Dates pick months, not days
startDate/endDate choose which months are loaded; recentDailyCosts is not trimmed to them. Filter by date yourself.
Example
GET /api/cost-explorer-details?entityType=resourceGroup&subscriptionId=<guid>&resourceGroupName=rg-prod&includeChildResources=true&startDate=2026-08-01&endDate=2026-10-15Cost Anomalies
GET /api/anomalies-summary
Permission: View Costs
Resources with unusual cost changes. No parameters. The API returns everything detected with wide thresholds; the UI narrows further on screen.
Response: { recommendations[], totalCount, totalResourcesAnalyzed, lastUpdatedScanId, lastUpdatedTimestamp }. totalResourcesAnalyzed is installation-wide. Each recommendation is one resource:
| Field | Meaning |
|---|---|
Identity fields, currency? | The resource |
anomalies[] | { anomalyType: spike | pattern-shift, anomalyDate, severity: low | medium | high, baselineCost, anomalyCost, changePercent, changeDollar (signed), description } |
totalAnomalies, highestSeverity, mostRecentDate | Summary |
totalCostImpact, mostRecentChangeDollar | Impact |
mostRecentIncrease, mostRecentDecrease | { date, changeDollar } or null |
anomalyTypeCounts, severityCounts | Dictionaries |
combinedCounts | Key type|severity |
directionCounts | { increase, decrease } |
Errors
- 500
Failed to retrieve cost anomaly recommendations
Example
GET /api/anomalies-summaryReservations and Savings Plans
Check the data tier before trusting utilization or expiry
StratoLens reads commitments with whatever billing access it has, and metadata.dataTier tells you how much detail that gave (see Permissions & Data Tier):
base: only spend from cost data. No utilization, expiry, SKU, term or scope.overallUtilizationPercentis 0 and every commitment counts as unknown utilization. Scripts must report "unknown", not "fully utilized" or "nothing expiring".partial: one of reservations or savings plans has full detail;partialTierMissingSidesays which one is missing.enhanced: everything.
GET /api/commitment-inventory
Permission: View Costs
Reservations and savings plans, with spend, utilization and expiry. No parameters.
Response: { inventory[], metadata, totalCount, lastUpdatedScanId, lastUpdatedTimestamp }.
inventory[] item
| Field | Meaning |
|---|---|
commitmentId, commitmentType, displayName | commitmentType is reservation or savingsPlan |
meterCategory, currency | Billing |
subscriptionIds[], subscriptionNames | Where it applies |
orderId, sku, reservedResourceType, quantity, location, instanceFlexibility | Purchase details |
commitmentAmountPerHour, commitmentCurrency | Savings plan commitment |
term | P1Y/P3Y/P5Y |
billingPlan | Monthly or Upfront |
scope, appliedScopes[] | Single | Shared | ManagementGroup |
benefitStartDate, expiryDate?, expiryDateTime?, provisioningState | Lifecycle. Reservations carry both expiry fields (expiryDate is an ISO date, expiryDateTime an ISO timestamp); savings plans carry only expiryDateTime. Use expiryDateTime when you need one field for both |
utilizationPercent1Day, utilizationPercent7Day, utilizationPercent30Day, utilizationTrend | 0-100, and UP | DOWN | STEADY |
monthlyCost, monthlyWastage | Spend |
metadata (or null before the first collection)
| Field | Meaning |
|---|---|
dataTier | base | partial | enhanced |
totalReservations, totalSavingsPlans | Counts |
overallUtilizationPercent | 0 at base tier |
totalMonthlyCommitmentSpend, totalEstimatedWastage, currency | Totals |
commitmentsWithUnknownUtilization, commitmentsWithUnknownCost | How much is unknown |
partialTierMissingSide? | Which side lacks detail at partial tier |
nearestExpiryDate?, nearestExpiryName? | Next expiry |
Notes
- For scoped users, totals are recalculated over what they can see.
- At base tier a commitment is visible to a scoped user only when one of its
subscriptionIdsis in that user's scope.
Errors
- 500The error text, or Failed to fetch commitment inventory
Example
GET /api/commitment-inventoryGET /api/commitment-coverage-recommendations
Permission: View Costs
What to buy, plus underutilized and expiring commitment alerts. No parameters. Hidden items are included.
Response: { recommendations[], metadata, totalCount, lastUpdatedScanId, lastUpdatedTimestamp }. Each item has a recommendationSubType of purchase, underutilized or expiring, and:
| Sub-type | Fields |
|---|---|
| Common | id, subscriptionId?, subscriptionName?, subscriptionIds? (copied from the parent commitment), currency?, scope?, appliedScopes? |
purchase | armSkuName, benefitType (ReservedInstances | SavingsPlan), recommendedQuantity, recommendedCommitmentAmountPerHour, term (P1Y or P3Y), lookBackPeriodDays (7, 30 or 60), location, reservedResourceType, estimatedOnDemandCost, estimatedSavingsPerMonth, savingsPercentage, coveragePercentage |
underutilized | commitmentId, commitmentName, commitmentType (reservation or savingsPlan), currentMonthlyCost, estimatedWastagePerMonth, currentUtilizationPercent, utilizationThresholdPercent, evaluationWindowDays (1, 7 or 30; absent on older documents, treat as 30) |
expiring | commitmentId, commitmentName, commitmentType (reservation or savingsPlan), currentMonthlyCost, expiryDate, daysUntilExpiry (as of the scan), evaluationWindowDays (1, 7 or 30; absent on older documents, treat as 30) |
metadata | { dataTier, totalPurchaseRecommendations, totalUnderutilizedAlerts, totalExpiringAlerts, totalPotentialSavingsPerMonth, totalWastagePerMonth, currency, lastEvaluationWindowDays? } |
Notes
- Base tier produces no expiring alerts.
- Tenant-wide shared purchase recommendations are visible to every scoped user.
Errors
- 500The error text, or Failed to fetch commitment coverage recommendations
Example
GET /api/commitment-coverage-recommendations