Skip to main content

Reference: Recommendations

Savings and hygiene recommendations, realized savings, access optimization and Azure Advisor. This is the data behind the VM Sizing, Orphaned Resources, Access Optimization and Advisor pages.

Whole set, no UI filters, hidden items included

Apart from realized savings and the Advisor routes, these routes take no parameters. They return the whole set in your scope, including items hidden in the UI, without the UI's on-screen filters. Filter in your script. Sizing amounts are USD list prices.

Right-Sizing

GET /api/vm-sizing-recommendations

Permission: View Resources

Right-sizing for VMs and scale sets.

Response: { recommendations[], metadata, totalCount, lastUpdatedScanId, lastUpdatedTimestamp }. metadata is { totalResourcesAnalyzed, totalResourcesWithRecommendations, aggressiveness } (installation-wide). Each recommendation:

vm-sizing-recommendations fields
FieldMeaning
Identity fieldsThe VM or scale set
recommendationTypevm_sku_downsize | vmss_sku_downsize
autoscaleEnabled?Scale sets
currentConfiguration{ sku, vCPUs, memoryGB, monthlyCost, pricingTier?, instanceCount? }
originalDetectedSku?, originalDetectedMonthlyCost?Before a partial change
recommendationSlots[]Up to 18 alternatives, each { slotType, excludeRestricted?, targetSku, vCPUs, memoryGB, monthlyCost, monthlySavings, annualSavings, savingsPercentage, rationale, restrictions?, allPricing?, confidence: { level: very_high | high | medium | low, score, factors[], warnings[], requiredActions? } }
utilizationEvidence{ analysisWindowDays, cpuUtilization { average, p95, maximum }, memoryUtilization?, memoryDataAvailable }
workloadProfile{ type, recommendedFamily, dominantResource, … }
severityhigh when default monthly savings are above $200, medium when above $50, else low
defaultMonthlySavings, bestMonthlySavingsSavings for the default slot and the best slot

Notes

  • The UI's default recommendation is the slot with slotType: "default" and no excludeRestricted.
  • Other slot types (crossFamily, olderGeneration, burstable, …) correspond to the UI's option toggles.

Errors

  • 500The error text, or Failed to fetch VM sizing recommendations

Example

GET /api/vm-sizing-recommendations

GET /api/disk-sizing-recommendations

Permission: View Resources

Managed disk tier and SKU recommendations. Same wrapper as VM sizing. Each recommendation:

disk-sizing-recommendations fields
FieldMeaning
recommendationTypedisk_sku_downsize
currentConfiguration{ sku, tier?, sizeGiB, provisionedIops, provisionedMBps, redundancy, isOsDisk, attachedVmId?, vmPowerState?, monthlyCost }
recommendationSlots[]Up to 4 (tierDowngrade, skuConversion, skuConversionHdd, v2Migration), each with targetSku, targetTier?, monthlyCost, monthlySavings, annualSavings, savingsPercentage, requiresVmDeallocation, oneWayMigration?, prerequisites?, rationale, confidence, warnings?
utilizationEvidence{ analysisWindowDays, metricsAvailable, iops?: { avgP95, peakP95, maximum }, throughputMBps?: { avgP95, peakP95, maximum }, vmDeallocatedDays?, vmDeallocatedSince? }. iops and throughputMBps are absent when metricsAvailable is false; the vmDeallocated* fields appear only for disks attached to a stopped VM
severity, defaultMonthlySavings, bestMonthlySavingsAs for VM sizing, with the same thresholds

Errors

  • 500The error text, or Failed to fetch disk sizing recommendations

Example

GET /api/disk-sizing-recommendations

Orphans, Retirements and Quotas

GET /api/orphaned-resource-recommendations

Permission: View Resources

Unattached, idle, stopped or empty resources.

Response: { recommendations[], totalCount, totalMonthlyCost, totalAnnualCost, availableFilters: { types[], statuses[] }, lastUpdatedScanId, lastUpdatedTimestamp }. Each recommendation:

orphaned-resource-recommendations fields
FieldMeaning
Identity fields, entityTyperesource or resourceGroup
recommendationTypeOne of: orphaned_public_ip, orphaned_managed_disk, orphaned_nic, orphaned_private_endpoint, orphaned_nsg, orphaned_route_table, idle_vpn_gateway, unattached_nat_gateway, idle_nat_gateway, idle_bastion, idle_application_gateway, stopped_vm_not_deallocated, idle_vm, stale_snapshot, empty_app_service_plan, empty_load_balancer, empty_application_gateway, orphaned_expressroute_circuit, orphaned_ddos_protection_plan, empty_sql_elastic_pool, empty_traffic_manager_profile, idle_traffic_manager_profile, orphaned_private_dns_zone, orphaned_frontdoor_waf_policy, empty_virtual_network, orphaned_ip_group, orphaned_firewall_policy, idle_container_registry, idle_storage_account, orphaned_api_connection, empty_subnet, empty_resource_group (new types may be added)
statusUnattached, Idle, Stopped (Not Deallocated), Stale, Empty
orphanedSince, orphanedDays, lastMetricDate?Timing
previousAttachment?What it was attached to
indirectOrphan?Orphaned because something in between is orphaned
monthlyCost?, annualCost?, rolling30DayCost?, currency?Cost
detectionEvidence{ detectionMethod: inventory | metrics, reason, … }
recommendation{ action: "review_and_remove", rationale }
detectedAtWhen detected

Notes

  • Totals add amounts in different currencies without conversion.
  • The UI's metrics-timeframe filter is not applied.

Errors

  • 500The error text, or Failed to fetch recommendations

Example

GET /api/orphaned-resource-recommendations

GET /api/service-retirement-recommendations

Permission: View Resources

Resources affected by upcoming Azure service retirements.

Response: { recommendations[], metadata, guidance, totalCount, lastUpdatedScanId, lastUpdatedTimestamp }. The last two are null when there is no data. Each recommendation has a tier:

service-retirement-recommendations fields
FieldMeaning
tieradvisorResource: confirmed for this exact resource (adds problemDescription, solutionDescription). resourceType: matched by resource type, unconfirmed. serviceHealth: an Azure Service Health event (adds eventTrackingId, eventStatus, eventSummary, impactStartTime, impactedServices[], impactedResourceCount, impactedResources[] up to 200)
CommonsubscriptionId, subscriptionName, title, retirementDate?, retirementFeatureName?, category?, impact?, activeInArg
Resource tiersAlso retirementTypeId, resourceId, resourceName, resourceType, resourceGroup
guidanceKeyed by retirementTypeId, each { detailedDescription?, potentialBenefits?, learnMoreLink?, serviceName? }
metadata{ catalogEntryCount, catalogLastUpdated, tier1Count, tier2Count, tier3Count, subscriptionsScanned, serviceHealthFailedSubscriptions }

Notes

  • The same resource can appear in more than one tier.
  • The UI hides type-level and undated items by default; the API does not.

Errors

  • 500The error text, or Failed to fetch service retirement recommendations

Example

GET /api/service-retirement-recommendations

GET /api/subscription-quota-recommendations

Permission: View Resources

Subscriptions near their vCPU quota limits. Only quotas at or above the threshold are listed.

Response: { recommendations[], metadata, totalCount, lastUpdatedScanId, lastUpdatedTimestamp }. Each recommendation is one subscription: { subscriptionId, subscriptionName, quotas[]: { location, quotaName, currentUsage, quotaLimit, usagePercent }, totalQuotaCount, highestUsagePercent, locationCount, locationSummaries[] }. metadata is { minimumUtilizationPercent, totalSubscriptionsScanned, subscriptionsWithQuotas }.

Errors

  • 500The error text, or Failed to fetch subscription quota recommendations

Example

GET /api/subscription-quota-recommendations

Realized Savings

GET /api/realized-savings-recommendations

Permission: per type: commitment_underutilized and commitment_expiring need View Costs; all other types need View Resources; a request mixing both needs both

Recommendations that were acted on, and the savings they produced. Kept about 2 years; removed if the issue comes back.

Parameters

realized-savings-recommendations parameters
NameRequiredNotes
typesyesCSV of orphaned_resource_cleanup, vm_sku_downsize, vmss_sku_downsize, disk_sku_downsize, access_optimization, commitment_underutilized, commitment_expiring. No spaces after commas

Errors

  • 400Missing required query parameter: types
  • 400Query parameter types must name at least one recommendation type
  • 400Invalid recommendation type(s): <list>
  • 403You do not have permission to read commitment realized savings recommendations.
  • 403You do not have permission to read these realized savings recommendations.
  • 500The error text, or Failed to fetch realized savings recommendations

Response: { recommendations[], totalCount, totalMonthlySavings, totalAnnualSavings, availableResolutionTypes[] }. Each recommendation:

realized-savings-recommendations fields
FieldMeaning
Identity fields, recommendationTypeWhat was acted on
resolutionTyperesource_deleted, resource_reattached, vm_deallocated, sku_changed, sku_partially_downsized, assignment_removed, eligibility_removed, commitment_utilization_improved, commitment_renewed
estimatedMonthlySavings, estimatedAnnualSavings0 for access optimization
currencyNull for access optimization
originalSnapshot{ label, recommendedTarget?, actualTarget?, originalMonthlyCost? }
resolvedAt, resolvedByScanIdWhen and in which scan

Example

GET /api/realized-savings-recommendations?types=orphaned_resource_cleanup,vm_sku_downsize,disk_sku_downsize

Access Optimization

GET /api/access-optimization-summary

Permission: View Resources

Evidence for unused, stale, over-scoped, over-privileged, sprawling or redundant role assignments. The labels themselves are not stored; derive them with the rules below. Rows inherited through an Entra group are dropped when that group's membership lookup fails (logged server-side only).

Response: { recommendations[], scopeMetadata, totalCount, lastUpdatedScanId, lastUpdatedTimestamp }. scopeMetadata is { subscriptionResourceGroupCounts, managementGroupChildCounts, managementGroupHierarchy, totalManagementGroups, totalSubscriptions }, narrowed to your scope. Each recommendation is one principal + role + scope:

access-optimization-summary fields
FieldMeaning
principalId, principalDisplayName, principalTypeWho
roleDefinitionName, roleDefinitionId, roleCategoryThe role
scope, scopeType, entityName?, subscriptionId, subscriptionName?, resourceGroupName?subscriptionId is "N/A" at management-group scope
createdOnAssignment date
grantedViaGroupId?, grantedViaGroupName?, grantedViaGroupChain?Inherited rows
pimEligible?, pimStartDateTime?, pimEndDateTime?PIM
lastActivityTimestamp, totalActivityCountOver 365 days
operationCounts, operationTypes[]{ create, update, delete, rbac, action }
lastRbacTimestampLast RBAC operation
scopeActivity[], activeScopes[], totalScopes, scopeEfficiencyWhere the principal was active
crossSubscriptionCountSubscriptions the principal has access to
redundancyReason?, redundancyDetails?Set when the assignment is redundant

Deriving labels (look-back window W days)

How the UI derives optimization labels for a look-back window of W days
Applies toLabelRule
PIM-eligible assignmentsunusedEligibilityOnly this label applies: not a group, role is critical or management, and no activity in W
Non-group principals with a critical or management roleunusedNo activity in W
staleOtherwise, when days since last activity > W × 0.5
overScopedActive scopes within W ÷ totalScopes < 30%, with at least one active scope
Non-group principals with a critical roleoverPrivilegedNo RBAC operation in W and the assignment is not already unused
Any principal, including groupsexcessiveSprawlcrossSubscriptionCount ≥ 5
redundantAssignmentredundancyReason is present

Errors

  • 500Failed to retrieve access optimization recommendations

Example

GET /api/access-optimization-summary

Azure Advisor

Shared Advisor values

  • Single-scan routes accept latest.
  • Category: Cost, HighAvailability, Performance, Security, OperationalExcellence. Impact: High, Medium, Low.
  • Hidden and legacy items are included. Legacy means the target resource is no longer in the inventory.

GET /api/getAdvisorRecommendationsSummary

Permission: View Advisor Recommendationsaccepts scanId=latest

Advisor recommendation counts, one row per resource × recommendation type.

Parameters

getAdvisorRecommendationsSummary parameters
NameRequired
scanIdyes

Errors

  • 400scanId parameter is required
  • 500Failed to retrieve advisor hierarchy summaries

Response: { compactSummaries[], availableFilters: { categories, impacts, resourceTypes }, totalCount }. Each row:

getAdvisorRecommendationsSummary row fields
FieldMeaning
entityId, displayName, entityTypeentityId is <originalResourceId>|<recommendationTypeId> (Azure casing; lower-case it before comparing with resourceId values from other routes)
subscriptionId, subscriptionName, resourceGroupName?, resourceType?Location
recommendationTypeId?, problemDescription?The recommendation type
totalCountRecommendations
categoryCounts, impactCounts, hiddenCounts, legacyCountsDictionaries
combinedCountsKey category|impact|hidden|legacy

Example

GET /api/getAdvisorRecommendationsSummary?scanId=latest

GET /api/getAdvisorRecommendationDetails

Permission: View Advisor Recommendationsaccepts scanId=latest

Advisor recommendations for one entity, ordered by category, then impact.

Parameters

getAdvisorRecommendationDetails parameters
NameRequiredNotes
scanId, subscriptionIdyessubscriptionId is required for every type
entityTypenoresource (default), resourceGroup, subscription
resourceGroupName, resourceNameper entityType

Errors

  • 400entityType must be one of subscription, resourceGroup, resource
  • 400subscriptionId and scanId parameters are required
  • 400resourceGroupName parameter is required for resourceGroup and resource queries
  • 400resourceName parameter is required for resource queries
  • 500Failed to retrieve advisor recommendation details

Response: { recommendations[], totalCount }. Each recommendation:

getAdvisorRecommendationDetails recommendation fields
FieldMeaning
recommendationIdStable across scans
recommendationName, recommendationTypeId?Type
category, impactClassification
problemDescription, solutionDescriptionText
Target and hierarchy fieldsThe resource
impactedField?, impactedValue?What Advisor looked at
extendedProperties?Cost recommendations include savingsAmount, savingsCurrency, annualSavingsAmount
lastUpdatedAdvisor's timestamp
isHidden?, notes?, isLegacy?Annotations and legacy flag

Example

GET /api/getAdvisorRecommendationDetails?scanId=latest&subscriptionId=<guid>&resourceGroupName=rg-data&resourceName=sqlsrv01

GET /api/getAdvisorRecommendationDiff

Permission: View Advisor Recommendations

Advisor recommendation change counts between two scans.

Parameters

getAdvisorRecommendationDiff parameters
NameRequired
baseScanId, compareScanIdyes

Errors

  • 400Please provide both 'baseScanId' and 'compareScanId' query parameters.
  • 404Base scan with ID '<id>' not found.
  • 404Compare scan with ID '<id>' not found.
  • 400Base scan '<base>' must be older than compare scan '<compare>'.
  • 500An error occurred while processing the advisor recommendation comparison.

Response: { summary: { baseScanId, compareScanId, baseScanTime, compareScanTime, added, deleted, modified }, recommendations: [] (always empty), hierarchySummary[] }. hierarchySummary is as in the Defender diffs.

Example

GET /api/getAdvisorRecommendationDiff?baseScanId=…&compareScanId=…

GET /api/getAdvisorRecommendationDiffDetails

Permission: View Advisor Recommendations

Paged changed Advisor recommendations for one entity. Order: added, modified, deleted.

Parameters

getAdvisorRecommendationDiffDetails parameters
NameRequiredNotes
baseScanId, compareScanId, subscriptionIdyes
resourceGroupName, resourceNamenoIncludes everything below that level
changeTypenoOne value
pagenoDefault 1
pageSizeno1-100, default 25

Errors

  • 400Missing-pair message, scan-pair 404/400
  • 400Please provide 'subscriptionId' query parameter.
  • 400Invalid 'changeType' parameter. Must be 'added', 'modified', or 'deleted'.
  • 400page must be a positive integer
  • 400pageSize must be between 1 and 100
  • 500An error occurred while processing the advisor recommendation diff details.

Response: { recommendations[], totalCount, totalPages, currentPage }. Each recommendation is a full one plus changeStatus and changes[].

Notes

  • For modified items, changes can be empty.

Example

GET /api/getAdvisorRecommendationDiffDetails?baseScanId=…&compareScanId=…&subscriptionId=<guid>&changeType=added&pageSize=50

Was this page helpful?