Daily Visibility Is the FinOps Foundation
Monthly invoices tell you what happened. Daily spend tracking tells you what is happening. The difference is the window you have to react: 30 days of accumulated surprise versus a same-day alert that costs have deviated from the expected pattern. For organizations running workloads across multiple Azure subscriptions — which is effectively every enterprise — daily spend tracking at the cross-subscription level provides the operational visibility that FinOps teams need to catch problems early and measure optimization efforts in near-real-time.
This guide covers the infrastructure for daily cross-subscription cost visibility: querying multiple subscriptions through management groups, building daily spend dashboards, configuring alerts for daily cost thresholds, and automating daily cost summary reports that reach stakeholders without requiring portal access.
Why FinOps Maturity Matters
Cloud financial management is not merely about reducing costs. It is about maximizing the business value of every dollar spent on cloud infrastructure. The FinOps Foundation defines three phases of cloud financial management maturity: Inform, Optimize, and Operate. This guide addresses practical implementation techniques that span all three phases.
In the Inform phase, organizations gain visibility into where their cloud spending goes. Azure Cost Management provides the raw data, but transforming that data into actionable insights requires structured approaches to tagging, cost allocation, and reporting. Without consistent resource tagging and cost center mapping, finance teams cannot attribute cloud costs to the business units that generate them, and engineering teams cannot identify which workloads are driving cost growth.
In the Optimize phase, teams actively reduce waste and improve efficiency. This includes rightsizing underutilized resources, eliminating orphaned resources, leveraging Reserved Instances and Savings Plans for predictable workloads, and implementing auto-scaling to match capacity with demand. The optimization opportunities identified through the Inform phase directly feed the actions in this phase.
In the Operate phase, FinOps practices become embedded in the organization’s standard operating procedures. Cost governance policies are enforced through Azure Policy, budget alerts trigger automated responses, and cost reviews are integrated into sprint planning and architectural decision-making. The goal is continuous financial optimization that happens as a natural part of engineering operations rather than as a periodic cleanup exercise.
Organizational Alignment
Effective cloud cost management requires collaboration between engineering, finance, and business leadership. Engineering teams understand the technical trade-offs between cost and performance. Finance teams understand the budget constraints and reporting requirements. Business leaders understand the revenue impact and strategic priorities that should drive investment decisions.
Establish a FinOps team or practice that brings these perspectives together. This cross-functional team should meet regularly to review spending trends, discuss optimization opportunities, and make joint decisions about investment priorities. The techniques in this guide provide the shared data foundation that enables these cross-functional conversations and ensures that cost decisions are informed by both technical and business context.
Create executive dashboards that translate technical cost data into business language. Instead of showing raw Azure meter costs, show cost per customer, cost per transaction, or cost as a percentage of revenue. These are the metrics that business leaders can act on and that connect cloud spending to business outcomes.
Management Group Scope: The Key to Cross-Subscription Queries
Azure Cost Management supports querying at the management group scope, which aggregates costs across all child subscriptions in a single query. Without this, you would need to query each subscription individually and combine the results — a fragile pattern that breaks every time a subscription is added or removed.
Management Group Hierarchy for Cost Tracking
A typical enterprise management group structure:
Tenant Root Group
├── mg-production
│ ├── sub-prod-east-001
│ ├── sub-prod-west-001
│ └── sub-prod-data-001
├── mg-non-production
│ ├── sub-dev-001
│ ├── sub-staging-001
│ └── sub-test-001
└── mg-shared-services
├── sub-networking-001
└── sub-security-001
Querying at the mg-production scope returns aggregated costs across all three production subscriptions. Querying at the Tenant Root Group returns costs for the entire organization. Assign the Cost Management Reader role at the management group level to give the FinOps team cross-subscription visibility without individual subscription role assignments.
Enabling Management Group Cost Visibility
By default, the Tenant Root management group may not have cost data access enabled. An EA administrator or billing account owner must enable it:
- Navigate to Cost Management + Billing → Billing scopes
- Select your billing account
- Go to Properties
- Set AO view charges and DA view charges to Enabled
For MCA accounts, similar settings exist under the billing profile properties.
Querying Daily Spend Across Subscriptions
Portal: Cost Analysis at Management Group Scope
- Navigate to Cost Management → Cost Analysis
- Change the scope to your management group (click the scope picker at the top)
- Select the Daily costs view or set granularity to Daily
- Group by Subscription to see per-subscription daily costs
- The date range maximum for daily granularity is 92 days (1 month at management group scope)
Management group scope queries support only 1 month of daily granularity data. For longer daily history, query individual subscriptions or use exported data in Azure Data Explorer.
REST API: Daily Costs by Subscription
POST https://management.azure.com/providers/Microsoft.Management/managementGroups/{mgGroupId}/providers/Microsoft.CostManagement/query?api-version=2025-03-01
{
"type": "ActualCost",
"timeframe": "MonthToDate",
"dataset": {
"granularity": "Daily",
"aggregation": {
"totalCost": {
"name": "PreTaxCost",
"function": "Sum"
}
},
"grouping": [
{
"name": "SubscriptionName",
"type": "Dimension"
}
]
}
}
The response contains rows with three values per entry: the date, subscription name, and cost amount. Parse these into a table or feed them into your dashboard platform.
PowerShell: Daily Spend Summary Script
# Daily spend summary across all subscriptions
$managementGroupId = "mg-production"
$today = Get-Date -Format "yyyy-MM-dd"
$yesterday = (Get-Date).AddDays(-1).ToString("yyyy-MM-dd")
$token = (Get-AzAccessToken -ResourceUrl "https://management.azure.com").Token
$body = @{
type = "ActualCost"
timeframe = "Custom"
timePeriod = @{
from = "${yesterday}T00:00:00Z"
to = "${today}T00:00:00Z"
}
dataset = @{
granularity = "Daily"
aggregation = @{
totalCost = @{ name = "PreTaxCost"; function = "Sum" }
}
grouping = @(
@{ name = "SubscriptionName"; type = "Dimension" }
)
}
} | ConvertTo-Json -Depth 5
$uri = "https://management.azure.com/providers/Microsoft.Management/managementGroups/$managementGroupId/providers/Microsoft.CostManagement/query?api-version=2025-03-01"
$result = Invoke-RestMethod -Uri $uri -Method POST -Headers @{ Authorization = "Bearer $token" } `
-Body $body -ContentType "application/json"
# Parse and display
$result.properties.rows | ForEach-Object {
[PSCustomObject]@{
Date = $_[0]
Subscription = $_[1]
DailyCost = [math]::Round($_[2], 2)
Currency = $_[3]
}
} | Sort-Object DailyCost -Descending | Format-Table -AutoSize
Building a Daily Spend Dashboard
Azure Dashboard with Pinned Cost Views
- Open Cost Analysis at the management group scope
- Set granularity to Daily, group by Subscription
- Select Pin to dashboard from the toolbar
- Repeat with different views: daily costs grouped by Service, daily costs grouped by Resource group
- Open your Azure dashboard — the pinned tiles show snapshot thumbnails of each view
Pinned tiles are snapshots, not live queries. Click the tile to open the full Cost Analysis view with current data. For a live dashboard, use Power BI, Grafana, or a custom web application that queries the Cost Management API on a schedule.
Power BI Daily Dashboard
The Azure Cost Management connector in Power BI supports management group scope. Set up a dataset with daily granularity, grouped by subscription, and configure scheduled refresh at 8 AM daily. The Power BI dashboard updates automatically each morning with yesterday’s costs.
Custom API-Based Dashboard
# Azure Function (timer trigger) - Daily cost aggregation
# Runs at 8 AM UTC every day, caches results for dashboard consumption
param($Timer)
$managementGroupId = $env:MANAGEMENT_GROUP_ID
$token = (Get-AzAccessToken -ResourceUrl "https://management.azure.com").Token
$yesterday = (Get-Date).AddDays(-1).ToString("yyyy-MM-dd")
$twoDaysAgo = (Get-Date).AddDays(-2).ToString("yyyy-MM-dd")
# Get yesterday's costs
$body = @{
type = "ActualCost"
timeframe = "Custom"
timePeriod = @{
from = "${twoDaysAgo}T00:00:00Z"
to = "${yesterday}T23:59:59Z"
}
dataset = @{
granularity = "Daily"
aggregation = @{
totalCost = @{ name = "PreTaxCost"; function = "Sum" }
}
grouping = @(
@{ name = "SubscriptionName"; type = "Dimension" }
)
}
} | ConvertTo-Json -Depth 5
$result = Invoke-RestMethod -Uri "https://management.azure.com/providers/Microsoft.Management/managementGroups/$managementGroupId/providers/Microsoft.CostManagement/query?api-version=2025-03-01" `
-Method POST -Headers @{ Authorization = "Bearer $token" } `
-Body $body -ContentType "application/json"
# Store in Table Storage for dashboard consumption
$rows = $result.properties.rows | ForEach-Object {
@{
PartitionKey = $_[0] # Date
RowKey = $_[1] # Subscription
DailyCost = $_[2]
Currency = $_[3]
}
}
# Write to Azure Table Storage (dashboard reads from here)
foreach ($row in $rows) {
Add-AzTableRow -Table $cloudTable -PartitionKey $row.PartitionKey `
-RowKey $row.RowKey -property @{ DailyCost = $row.DailyCost; Currency = $row.Currency }
}
Advanced Cost Optimization Techniques
Beyond the basic optimization strategies, consider these advanced techniques that can yield significant additional savings.
Spot Instances and Low-Priority VMs: For fault-tolerant batch processing, machine learning training, dev/test environments, and CI/CD build agents, use Azure Spot VMs that offer up to 90 percent discount compared to pay-as-you-go pricing. Implement graceful shutdown handlers that checkpoint progress when Azure reclaims the capacity, and design your workloads to resume from the last checkpoint on a new instance.
Reserved Instance Exchange and Return: Azure Reservations can be exchanged for different VM families, regions, or terms without penalty. If your workload characteristics change, exchange your existing reservation rather than letting it go unused. This flexibility makes reservations less risky than they might appear, as you can adjust your commitments as your infrastructure evolves.
Hybrid Benefit: If your organization has existing Windows Server or SQL Server licenses with Software Assurance, apply Azure Hybrid Benefit to reduce VM and managed database costs by up to 80 percent when combined with Reserved Instances. Track license utilization to ensure you are maximizing the value of your existing license investments.
Resource Lifecycle Automation: Implement automation that shuts down development and testing environments outside of business hours and weekends. A typical dev/test VM that runs 10 hours per day, 5 days per week costs 70 percent less than one that runs 24/7. Azure Automation schedules, Azure DevTest Labs auto-shutdown, and Azure Functions with timer triggers can all implement this pattern with minimal effort.
Right-Sizing Based on Actual Usage: Azure Advisor provides right-sizing recommendations based on CPU and memory utilization over the past 14 days. Review these recommendations weekly and act on them. A VM that consistently uses less than 20 percent of its allocated CPU should be downsized to the next smaller SKU. For databases, review DTU or vCore utilization and adjust the service tier accordingly.
Daily Cost Alerts
Budget-Based Daily Monitoring
While Azure budgets operate on monthly, quarterly, or annual cycles, you can create a daily cost threshold pattern by setting a monthly budget with a forecasted threshold. A forecasted 100% alert on a monthly budget effectively detects when daily run rate would exceed the monthly target.
For more granular daily alerting, combine the Query API with an Azure Function:
# Azure Function - Daily cost threshold check
# Triggers if any subscription's daily cost exceeds the defined threshold
$thresholds = @{
"sub-prod-east-001" = 1500 # $1,500/day max
"sub-prod-west-001" = 800
"sub-dev-001" = 300
}
# Query yesterday's actual costs per subscription
$yesterday = (Get-Date).AddDays(-1).ToString("yyyy-MM-dd")
# ... (Query API call as shown above)
$alerts = @()
foreach ($row in $result.properties.rows) {
$subscriptionName = $row[1]
$dailyCost = $row[2]
if ($thresholds.ContainsKey($subscriptionName) -and $dailyCost -gt $thresholds[$subscriptionName]) {
$alerts += [PSCustomObject]@{
Subscription = $subscriptionName
DailyCost = [math]::Round($dailyCost, 2)
Threshold = $thresholds[$subscriptionName]
Exceeded = [math]::Round($dailyCost - $thresholds[$subscriptionName], 2)
}
}
}
if ($alerts.Count -gt 0) {
# Send alert via Teams webhook, email, or PagerDuty
$message = "Daily Azure cost threshold(s) exceeded:`n"
$alerts | ForEach-Object { $message += "- $($_.Subscription): `$$($_.DailyCost) (limit: `$$($_.Threshold), over by `$$($_.Exceeded))`n" }
Invoke-RestMethod -Uri $teamsWebhookUrl -Method POST `
-Body (@{ text = $message } | ConvertTo-Json) -ContentType "application/json"
}
Anomaly Alerts Across Subscriptions
Deploy anomaly alert rules to every subscription automatically. Since anomaly detection only works at the subscription scope (not management groups), you need one alert rule per subscription:
# Deploy anomaly alerts to all child subscriptions of a management group
$mgGroup = Get-AzManagementGroup -GroupId "mg-production" -Expand
$subscriptionIds = $mgGroup.Children | Where-Object { $_.Type -eq "/subscriptions" } | Select-Object -ExpandProperty Name
foreach ($subId in $subscriptionIds) {
Set-AzContext -SubscriptionId $subId
$body = @{
kind = "InsightAlert"
properties = @{
displayName = "Daily Anomaly Alert"
notification = @{
subject = "Cost anomaly detected"
to = @("finops@contoso.com")
}
schedule = @{
endDate = "2027-12-31T00:00:00Z"
frequency = "Daily"
startDate = (Get-Date -Format "yyyy-MM-ddT00:00:00Z")
}
status = "Enabled"
viewId = "/providers/Microsoft.CostManagement/views/ms:DailyAnomalyByResourceGroup"
}
} | ConvertTo-Json -Depth 5
$token = (Get-AzAccessToken -ResourceUrl "https://management.azure.com").Token
Invoke-RestMethod `
-Uri "https://management.azure.com/subscriptions/$subId/providers/Microsoft.CostManagement/scheduledActions/finops-anomaly-alert?api-version=2025-03-01" `
-Method PUT -Headers @{ Authorization = "Bearer $token" } `
-Body $body -ContentType "application/json"
Write-Host "Anomaly alert deployed to subscription: $subId"
}
Automated Daily Email Reports
Deliver a formatted daily cost summary to stakeholders every morning using the Scheduled Actions API or a custom Azure Function.
Scheduled Actions: Daily Cost Email
# Create a daily cost report email at management group scope
$body = @{
kind = "Email"
properties = @{
displayName = "Daily Cost Summary - All Subscriptions"
notification = @{
subject = "Daily Azure Cost Report"
to = @("finops@contoso.com", "cfo@contoso.com")
message = "Daily cost summary across all production subscriptions"
}
schedule = @{
frequency = "Daily"
startDate = (Get-Date -Format "yyyy-MM-ddT00:00:00Z")
endDate = (Get-Date).AddYears(1).ToString("yyyy-MM-ddT00:00:00Z")
}
status = "Enabled"
viewId = "/providers/Microsoft.Management/managementGroups/mg-production/providers/Microsoft.CostManagement/views/daily-all-subs"
}
} | ConvertTo-Json -Depth 5
$token = (Get-AzAccessToken -ResourceUrl "https://management.azure.com").Token
Invoke-RestMethod `
-Uri "https://management.azure.com/providers/Microsoft.Management/managementGroups/mg-production/providers/Microsoft.CostManagement/scheduledActions/daily-cost-summary?api-version=2025-03-01" `
-Method PUT -Headers @{ Authorization = "Bearer $token" } `
-Body $body -ContentType "application/json"
Day-Over-Day and Week-Over-Week Comparison
# Compare today vs. yesterday vs. same day last week
$subscriptions = Get-AzSubscription | Where-Object { $_.State -eq "Enabled" }
$today = (Get-Date).AddDays(-1) # Yesterday (latest available data)
$dayBefore = $today.AddDays(-1)
$lastWeek = $today.AddDays(-7)
function Get-DailyCost([string]$subscriptionId, [datetime]$date) {
$from = $date.ToString("yyyy-MM-ddT00:00:00Z")
$to = $date.AddDays(1).ToString("yyyy-MM-ddT00:00:00Z")
$body = @{
type = "ActualCost"
timeframe = "Custom"
timePeriod = @{ from = $from; to = $to }
dataset = @{
granularity = "None"
aggregation = @{ totalCost = @{ name = "PreTaxCost"; function = "Sum" } }
}
} | ConvertTo-Json -Depth 5
$result = Invoke-RestMethod `
-Uri "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.CostManagement/query?api-version=2025-03-01" `
-Method POST -Headers @{ Authorization = "Bearer $token" } `
-Body $body -ContentType "application/json"
return [math]::Round(($result.properties.rows[0][0]), 2)
}
$report = foreach ($sub in $subscriptions) {
$todayCost = Get-DailyCost -subscriptionId $sub.Id -date $today
$yesterdayCost = Get-DailyCost -subscriptionId $sub.Id -date $dayBefore
$lastWeekCost = Get-DailyCost -subscriptionId $sub.Id -date $lastWeek
[PSCustomObject]@{
Subscription = $sub.Name
Yesterday = $todayCost
DayBefore = $yesterdayCost
DayOverDay = if ($yesterdayCost -gt 0) { "{0:P1}" -f (($todayCost - $yesterdayCost) / $yesterdayCost) } else { "N/A" }
LastWeekSameDay = $lastWeekCost
WeekOverWeek = if ($lastWeekCost -gt 0) { "{0:P1}" -f (($todayCost - $lastWeekCost) / $lastWeekCost) } else { "N/A" }
}
}
$report | Format-Table -AutoSize
Cost Data Freshness and Timing
Understanding Azure cost data latency is critical for setting realistic expectations on daily tracking:
| Data Type | Freshness | Notes |
|---|---|---|
| Usage data (PAYG) | 8-24 hours | Some resource providers report faster than others |
| Usage data (EA) | Up to 24 hours | EA data may have additional reconciliation delay |
| Reservation purchases | Same day | Appear in actual cost on purchase date |
| Marketplace charges | Up to 48 hours | Third-party billing cycles vary |
| Budget evaluations | Every 24 hours | Alert email within 1 hour of threshold breach |
| Anomaly detection | 36 hours after day end | Evaluated daily; subscription scope only |
Schedule daily cost reports for 8-10 AM UTC to ensure the previous day’s data has been fully ingested. Running reports at midnight will produce incomplete numbers because usage from late in the day may not have been processed yet.
Governance and Automation
Manual cost management does not scale. As your Azure footprint grows beyond a handful of subscriptions, you need automated governance to maintain cost discipline.
Azure Policy can enforce tagging requirements at deployment time, ensuring that every resource is tagged with the cost center, environment, application name, and owner before it is created. Without consistent tagging, cost allocation becomes a manual, error-prone guessing game. Define a mandatory tag set and use a deny policy effect to prevent untagged resources from being deployed.
Budget alerts with action groups can trigger automated responses when spending thresholds are crossed. At 80 percent of budget, send a notification to the engineering team lead. At 100 percent, notify the engineering manager and finance partner. At 120 percent, trigger an automated workflow that inventories recently created resources and flags potential cost anomalies for immediate review.
Consider implementing a cost anomaly detection pipeline. Azure Cost Management provides anomaly detection capabilities that flag unusual spending patterns. Supplement this with custom KQL queries in Log Analytics that monitor resource creation events, SKU changes, and scaling operations. When an anomaly is detected, an automated investigation workflow can gather the relevant context (who created the resource, which pipeline deployed it, what business justification was provided) and route it to the responsible team for review.
Regular cost optimization reviews should be scheduled on a monthly cadence. Use the Azure Advisor cost recommendations as a starting point, then layer in your organization-specific optimization criteria. Track optimization actions and their measured impact over time to demonstrate the ROI of your FinOps program to leadership. A well-run FinOps program typically achieves 20 to 30 percent cost reduction in the first year, with ongoing annual optimization of 5 to 10 percent as the program matures.
Production Checklist
- Management group structure: Organize subscriptions into management groups that align with your reporting needs
- RBAC: Assign Cost Management Reader at the management group level for FinOps team members
- Cost visibility: Enable charge visibility settings in the billing account properties
- Saved views: Create daily cost views at management group scope, grouped by subscription
- Anomaly alerts: Deploy to every subscription (automated script)
- Budget alerts: Per-subscription monthly budgets with forecasted thresholds
- Daily reports: Scheduled Actions for automated email delivery at 8 AM UTC
- Custom dashboard: Azure Function + Table Storage + web frontend for real-time daily tracking
- Comparison metrics: Day-over-day and week-over-week change tracking for trend analysis
For more details, refer to the official documentation: What is Microsoft Cost Management.