Reference: Reports
The same data as StratoLens Reports, as JSON. Every route is under /api/reporting-vnext/ and takes the shared filters below. Security and change reports need View Resources; cost reports need View Costs.
10 Routes
reporting-vnext/governance-auditreporting-vnext/public-exposurereporting-vnext/privileged-accessreporting-vnext/access-driftreporting-vnext/security-config-driftreporting-vnext/infrastructure-changereporting-vnext/orphan-wastereporting-vnext/forecastreporting-vnext/trend-variancereporting-vnext/spend-accountability
Shared Parameters
All filters are optional and combined with AND. URL-encode each value once, and always encode + as %2B.
Filters
| Parameter | Format | Rules |
|---|---|---|
filterScopes | JSON array of { "type": "managementGroup" | "subscription" | "resourceGroup", "id": "…" } | id is the management group ID (includes everything below it), the subscription GUID, or "<subscriptionGuid>/<rgName>" for a resource group. Several scopes are ORed. An unknown or out-of-scope ID gives an empty report, not an error |
filterTypes | JSON array of ARM types | Case-insensitive, e.g. "microsoft.compute/virtualmachines" |
filterLocations | JSON array of regions | Case-sensitive, e.g. "eastus" |
filterName | Plain string | Case-insensitive substring of the resource name |
filterTags | JSON array of { "id": "<any>", "key": "<tag>", "value": "<v>", "operator": "equals" | "exists" | "missing" } | Same shape as Cost Explorer tagFilters |
filterTagMode | and | or | Default and |
- Invalid elements are dropped without an error. A dimension with nothing valid left is ignored.
- Once a type, location, name or tag filter is active, rows not tied to a specific resource (subscription-level charges or changes) are excluded.
- Non-scope filters, and a
filterScopesentry of typeresourceGroup, make cost and change reports compute live: same numbers, slower.spend-accountabilityalso computes live for any date range that is not one full calendar month or the current month to date.
Other parameters
| Parameter | Rules |
|---|---|
windowDays | governance-audit allows 30, 60, 90. access-drift, security-config-drift and infrastructure-change allow 7, 30, 60, 90. Any other value is treated as 30 |
splitTagKey | A tag key to break costs down by. __none__ or an empty value means no split; with no split there is one "All resources" bucket. Resources missing the tag go to an "Unallocated" bucket. Defaults when omitted: spend-accountability uses the first tag key; governance-audit uses the configured budget tag; forecast and trend-variance use no split |
Errors and caps
- The only
400s in this group are spend-accountability's date errors. Each route's500message is listed with it. - Lists marked "(cap N)" return at most N items. Most have a sibling
…Totalfield with the full count; the exceptions are noted per route. KPIs always use the full set.
Security and Access Reports
GET /api/reporting-vnext/public-exposure
Permission: View Resources
Resources reachable from the internet, by category and severity. Takes the filters.
Response
| Field | Meaning |
|---|---|
generatedAt | When |
scanId, scanTimestamp, priorScanId, priorScanTimestamp | Scans used |
deltaAvailable, hasData | Flags |
kpis | { totalExposed, bySeverity { critical, high, medium }, publicEndpoints, exposedDataStores, wideOpenRules, newSinceLastScan } |
categorySummary[] | All 6 categories: publicIp, nsgOpenRule, storagePublic, sqlPublic, keyVaultPublic, appServicePublic |
resources[] (cap 300), resourcesTotal | Each row: { resourceId, name, resourceType, category, reason, severity, subscriptionId?, subscriptionName?, resourceGroup?, location?, detail?, isNew }. Severities: critical, high, medium |
notes[] | Caveats |
Notes
resourcesrows are findings, so one NSG can appear once per open rule.kpis.totalExposedcounts distinct resources.isNewis computed against the unfiltered prior scan, so a resource that only entered the current filter since the last scan is not marked new.
Errors
- 500
Failed to retrieve public exposure report
Example
GET /api/reporting-vnext/public-exposureGET /api/reporting-vnext/privileged-access
Permission: View Resources
Standing (permanent) privileged assignments. PIM-activated ones are excluded and counted separately. Of the filters, only filterScopes applies.
Response
| Field | Meaning |
|---|---|
generatedAt, scanId, scanTimestamp, hasData | Context |
kpis | { privilegedIdentities, userCount, groupCount, servicePrincipalCount, criticalIdentities, managementGroupScopeAssignments, subscriptionScopeAssignments, totalPrivilegedAssignments, oldestStandingDays?, pimManagedActiveExcluded } |
scopeTypeBreakdown[] | 4 rows |
principals[] (cap 200), servicePrincipals[] (cap 100) | Each with a …Total |
notes[] | Caveats |
Principal row
| Field | Meaning |
|---|---|
principalId, principalDisplayName, principalType | Who |
highestCategory | critical | management |
topRoleName | The most privileged role, for example Owner |
criticalCount, managementCount | Counts |
| Per-level scope counts | Assignments per scope level |
scopes[] (cap 40) | { scopeType, entityId, entityName, roleName, roleCategory, createdOn?, subscriptionName? }. No …Total sibling: the four per-level scope counts give the full count |
oldestCreatedOn?, standingDays? | Age |
Notes
- A
filterScopesentry of typeresourceGroupalso matches assignments granted at the resource group's parent subscription, because that access is inherited by the resource group.
Errors
- 500
Failed to retrieve privileged access report
Example
GET /api/reporting-vnext/privileged-accessGET /api/reporting-vnext/access-drift
Permission: View Resources
Grants and revocations over a window. Compares the latest scan with the latest scan at least windowDays older. Takes scope filters only, and windowDays. Unlike privileged-access, PIM-activated assignments are included.
Response
| Field | Meaning |
|---|---|
generatedAt, scanId, scanTimestamp, windowDays | Context |
priorScanId, priorScanTimestamp | The older scan |
windowTruncated | True when no scan was old enough |
deltaAvailable, hasData | With only one scan, deltaAvailable is false |
kpis | { newGrants, newCriticalGrants, newlyEmpoweredIdentities, revokedGrants, attributedGrants } |
scopeBreakdown[] | Per scope level |
grants[], revocations[] (cap 200 each, with …Total) | Each entry: { changeType: granted | revoked, principalId, principalDisplayName, principalType, roleName, roleCategory, scopeType, scopeName, subscriptionName?, createdOn?, actor?, actorAt? } |
notes[] | Caveats |
Notes
- A
filterScopesentry of typeresourceGroupalso matches assignments granted at the resource group's parent subscription, because that access is inherited by the resource group.
Errors
- 500
Failed to retrieve access drift report
Example
GET /api/reporting-vnext/access-drift?windowDays=7GET /api/reporting-vnext/security-config-drift
Permission: View Resources
Changes that weakened a tracked security setting. Takes the filters and windowDays.
Response
| Field | Meaning |
|---|---|
generatedAt, windowDays, windowStart, windowEnd, earliestChange, hasData | Context |
updatesScanned | Changes examined |
kpis | { totalDrift, criticalDrift, highDrift, mediumDrift, affectedResources, unattributed } |
categorySummary[] | publicAccess, networkExposure, transportSecurity, dataProtection |
findings[] (cap 100), findingsTotal | Each finding: { resourceId, resourceName, resourceType, category, severity, title, setting, beforeValue, afterValue, reason, changeTime, actor?, actorKind, subscriptionName?, resourceGroupName? } |
notes[] | Caveats |
Errors
- 500
Failed to retrieve security config drift report
Example
GET /api/reporting-vnext/security-config-drift?windowDays=60Change and Waste Reports
GET /api/reporting-vnext/infrastructure-change
Permission: View Resources
Change volume, velocity and change-makers over a window. Takes the filters and windowDays.
Response
| Field | Meaning |
|---|---|
generatedAt, window fields, earliestChange, hasData | Context |
kpis | { totalChanges, createCount, updateCount, deleteCount, securityRelevantChanges, securityRelevantDeletions, activeChangeMakers, humanChangeMakers, automationChangeMakers, systemChangeMakers, attributedChanges } |
velocity[], velocityGranularity | { start, counts { create, update, delete, total } } per day (windows ≤ 30 days) or per week |
actorKindCounts | Human, automation, system |
subscriptions[], resourceGroups[] (cap 12 each) | Where changes happened |
securityCategories[] | Security-relevant changes by category |
resourceTypes[] (cap 25) | What changed |
changeMakers[] (cap 15) | { actor, actorKind, counts, securityRelevantCount }. Includes synthetic Unknown and Azure system rows for unattributed changes. The activeChangeMakers, humanChangeMakers, automationChangeMakers and systemChangeMakers KPIs count named actors only, so they do not add up to the row count |
notes[] | Caveats |
Errors
- 500
Failed to retrieve infrastructure change report
Example
GET /api/reporting-vnext/infrastructure-change?windowDays=30GET /api/reporting-vnext/orphan-waste
Permission: View Resources
Orphaned resources and the money they waste. Takes the filters. Unlike orphaned-resource-recommendations, this report excludes items hidden in the UI and metrics-based detections with activity in the last 30 days.
Response
| Field | Meaning |
|---|---|
generatedAt, hasData, lastScanId, lastScanTimestamp | Context |
currency, mixedCurrency | Currency |
metricsGateDays, inventoryCount, metricsCount | How detections were gated |
kpis | { orphanCount, costedCount, freeCount, totalMonthlyCost, totalAnnualCost, longIdleCount, oldestOrphanDays } |
wastage, orphaned | Orphans with a cost, and zero-cost orphans. Each holds resources[] (cap 100, with resourcesTotal), byType[] (cap 25, with byTypeTotal), bySubscription[] (cap 12, with bySubscriptionTotal), ageBands[] (lt30, d30to90, d90to180, gte180), count, totalMonthlyCost, totalAnnualCost |
| Resource row | { resourceName, resourceType, recommendationType, recommendationLabel, status, reason, detectionMethod, monthlyCost, annualCost, currency, orphanedDays, orphanedSince, subscriptionId, subscriptionName, resourceGroupName } |
notes[] | Caveats |
Errors
- 500
Failed to retrieve orphans & waste report
Example
GET /api/reporting-vnext/orphan-wasteCost Reports
GET /api/reporting-vnext/governance-audit
Permission: View Costs
Change accountability, budget enforcement and audit integrity in one report. Takes the filters, windowDays and splitTagKey.
Response
| Field | Meaning |
|---|---|
currency?, mixedCurrency, asOfDate, generatedAt | Context |
windowDays, windowStart, windowEnd, earliestChange | earliestChange later than windowStart means less history than the window |
availableTagKeys[] | Tag keys you can split by |
kpis | { totalChanges, createCount, updateCount, deleteCount, budgetBreachCount, budgetAtRiskCount, namedAccountabilityPercent, netMonthlyCostImpact } |
changeActors[] (cap 12) | { actor, createCount, updateCount, deleteCount, totalCount, costImpact }. No …Total sibling |
changeToCost | { newResourceMonthlyCost, newResourceCount, retiredResourceMonthlyCost, retiredResourceCount, netMonthlyCostImpact, updateCount, costMatchedCount, changedResourceCount, note } |
budgetEnforcement | { monthLabel, budgetTagKey, alertThresholdPercent, overCount, atRiskCount, underCount, noBudgetCount, totalBudget, totalProjected, totalVariance, anyBudgetSet, rows[] }. Always covers the current calendar month, regardless of windowDays. Row shape is as in trend-variance |
auditIntegrity | { namedAccountabilityPercent, changesWithNamedActor, totalChanges, unattributedChangeCount, auditEntryCount, auditByType[] } |
Notes
auditEntryCountandauditByTypeare product audit-log counts for the whole installation, not narrowed by scope. They arenullunless the caller also holds View Audit Logs.auditByTypecounts the newest 2000 entries in the window;auditEntryCountis the full total.
Errors
- 500
Failed to retrieve governance & audit report
Example
GET /api/reporting-vnext/governance-audit?windowDays=90&splitTagKey=CostCenterGET /api/reporting-vnext/forecast
Permission: View Costs
Spend forecast from a trailing 14-day run rate with seasonality. Takes the filters and splitTagKey.
Response
| Field | Meaning |
|---|---|
currency?, mixedCurrency, asOfDate, method, trailingWindowDays, seasonalityApplied, historyMonths | Context |
month | { label, monthKey, monthToDate, daysElapsed, daysInMonth, trailingDailyAvg, projectedTotal, projectedLow, projectedHigh, priorMonthActual? } |
quarter, year | { label, startDate, endDate, actualToDate, projectedTotal, projectedLow, projectedHigh, monthsRemaining, daysElapsed, daysInPeriod, priorPeriodActual?, series[], breakdown } |
rolling | { next30, next90, next365 } |
annualizedRunRate | Yearly run rate |
series[] | { date, actual?, forecast?, low?, high? } |
breakdown | { splitTagKey, displayName, buckets[]: { value, isUnallocated, monthToDate, projectedTotal } } |
splitTagKey, availableTagKeys[] | Split in effect, and alternatives |
Errors
- 500
Failed to retrieve forecast
Example
GET /api/reporting-vnext/forecast?splitTagKey=BusinessUnitGET /api/reporting-vnext/trend-variance
Permission: View Costs
Month-over-month trend, budget vs. actual and a cost bridge. Takes the filters and splitTagKey.
Response
| Field | Meaning |
|---|---|
currency?, mixedCurrency, asOfDate | Context |
budgetTagKey, alertThresholdPercent, splitTagKey, availableTagKeys[] | Configuration in effect |
trend | { series[]: { monthKey, label, actual?, projected?, isCurrent }, momPercent?, momLabel?, qoqPercent?, qoqLabel? }: up to 12 months |
budgetVsActual | { monthLabel, totalBudget, totalActualMTD, totalProjected, totalVariance, rows[], anyBudgetSet, budgetCoversAllBuckets, filterNarrowsActuals } |
| Budget row | { value, isUnallocated, budget, hasBudget, actualMTD, projectedMonthEnd, variance, variancePercent, status: over | at-risk | under | no-budget } |
bridge | Null, or { fromLabel, toLabel, fromTotal, toTotal, segments[]: { key: start | newResources | retiredResources | skuConfig | usageRate | end, label, amount, runningTotal }, note? } |
subscriptionVariance[] | { currency, subscriptions[]: { subscriptionId, subscriptionName, monthlyCosts, monthlyVariancePercent, totalCost }, monthlyTotals, grandTotal } |
subscriptionVarianceMonthRange[] | Months covered |
Errors
- 500
Failed to retrieve trend & variance report
Example
GET /api/reporting-vnext/trend-variance?splitTagKey=__none__GET /api/reporting-vnext/spend-accountability
Permission: View Costs
Spend broken down by a tag key, with the unallocated share. Takes startDate/endDate (both or neither; default month to date; max 366 days), the filters and splitTagKey.
Errors
- 400
Both startDate and endDate are required. - 400
Invalid date format. Use YYYY-MM-DD format. - 400
startDate must not be after endDate - 400
Invalid calendar date. - 400
Date range exceeds maximum supported span of 366 days - 500
Failed to retrieve spend accountability report
Response
| Field | Meaning |
|---|---|
periodStart, periodEnd | The range used |
currency?, mixedCurrency | Currency |
totalCost, totalAmortizedCost, totalResourceCount | Totals |
breakdown | { splitTagKey, displayName, buckets[], unallocatedCost, unallocatedPercent, allocatedPercent }. Each bucket: { value, isUnallocated, cost, amortizedCost, percentOfTotal, resourceCount, subscriptions[]: { subscriptionId, subscriptionName, cost, amortizedCost } }, highest cost first |
splitTagKey, availableTagKeys[], generatedAt | Split in effect, alternatives, and when |
Example
GET /api/reporting-vnext/spend-accountability?startDate=2026-09-01&endDate=2026-09-30&splitTagKey=CostCenter