Reference: Scans, Inventory & Performance Metrics
Read scan history and the inventory a scan discovered. This is the data behind the Automated Scanning, Resource Explorer, Role Assignments and Performance Metrics pages. Scan ID, entity selector and paging rules are in Conventions.
13 Routes
List the subscriptions you can see
getScanResults?entityTypes=subscription is the quickest way to list every subscription inside your scope.
Identity and Scans
GET /api/whoami
Permission: any signed-in user
Check who you are signed in as and what you can do. Use it as the first call when troubleshooting. No parameters.
Response: { "success": true, "user": { … } }. The user fields:
| Field | Meaning |
|---|---|
id, userId | Your Entra object ID |
displayName | Your name, or "Unknown User" |
permissions[] | Your effective permission IDs, e.g. "data.read", "cost.read" |
roles[] | Always empty; use permissions |
scopeContext | The scope applied to your calls: { mode: "all" | "include", allowedSubscriptionIds?, allowedManagementGroupIds? } |
roleBasedScopeContext | Your admin-granted scope; the same as scopeContext for API calls |
accessChannel | "external" for API tokens |
externalClientId | The trusted client you called from |
externalApiAccess | Whether any of your grants carries the External API access flag |
grantingSources[] | { type: "direct" | "group", groupId?, groupName? }: which grants give you access |
createdAt | Time of this request (not account creation) |
Errors
- 500
Failed to retrieve user information.
Example
GET /api/whoamiGET /api/getAllScans
Permission: View Scans
List finished scans, newest first. No parameters. The response is a JSON array, not paged and not scope-narrowed. In-progress scans are excluded.
Each item
| Field | Meaning |
|---|---|
scanId (= id) | Scan ID |
scanTimestamp | Start time |
status | Completed, Failed, CompletedWithNoResults, Cancelled |
statusMessage | Final status text |
completedTimestamp | Finish time (may be absent on failed or cancelled scans) |
totalResources | Resources found |
warningCount, hasErrorWarnings | Warning summary; details via getScanWarnings |
entitySummary | { managementGroups, subscriptions, resourceGroups, resources, total } (may be absent on old scans) |
activityLogBackfillStatus, activityLogBackfillMgStatus, costBackfillStatus, resourceGraphBackfillStatus | Historical-data backfill status, e.g. "pending"; absent when none |
Errors
- 500
Failed to retrieve scan list.
Example
GET /api/getAllScansGET /api/getScanStatus
Permission: View Scans
Status and progress of one scan, including a running one.
Parameters
| Name | Required | Notes |
|---|---|---|
scanId | yes | Scan ID |
Errors
- 400
Missing required parameter: scanId - 500
Failed to retrieve scan status.(also for an unknown scan)
Response
| Field | Meaning |
|---|---|
scanId, status | status is InProgress, Completed, Failed, Cancelled or CompletedWithNoResults |
statusMessage | Status text |
scanTimestamp, completedTimestamp | completedTimestamp is null while running |
totalResources | Resources found |
triggeredBy | manual, scheduled, unknown |
lastUpdated | Last status update |
error | Error detail or null |
progress | Raw progress record (free-form) |
standardizedProgress | Null when finished. While running: { overall: { phaseIndex, totalPhases, percentComplete }, currentPhase: { name, displayName, startTime, status, progress: { primary: { current, total, label }, secondary, context } }, metadata } |
isRemoteScan | True when the scan runs in another container (simplified progress) |
pendingBackfills | { activityLog, cost, resourceGraph } booleans; present only when one is pending |
Example
GET /api/getScanStatus?scanId=scan-2026-10-14T02%3A00%3A00.000ZGET /api/getScanWarnings
Permission: View Scans
The warnings a scan recorded, such as missing access or failed collectors. Not scope-narrowed. The response is a JSON array (possibly empty).
Parameters
| Name | Required | Notes |
|---|---|---|
scanId | yes | Scan ID |
Errors
- 400
Missing required parameter: scanId - 404
Scan not found: <scanId> - 500
Failed to retrieve scan warnings.
Each item
| Field | Meaning |
|---|---|
type | Warning kind, e.g. resource_access_limited, arg_query_failure, defender_collection_failure, cost_collection_failures, principal_resolution_failed, policy_collection_failure, cve_catalog_sync_failure (about 20 values) |
severity | info, warning, error (optional) |
details | What went wrong |
timestamp | When |
resourceId, subscriptionId | What caused it (optional) |
count | Errors folded into this warning (optional) |
errorCode | E.g. an HTTP status code (optional) |
context | Extra detail (optional) |
Example
GET /api/getScanWarnings?scanId=…Inventory
getScanResults repeats the manifest on every page
The full scan manifest is sent with every page. Read it once.
GET /api/getScanResults
Permission: View Resources
The inventory discovered by a scan (management groups, subscriptions, resource groups, resources), paged.
Parameters
| Name | Required | Values | Default | Notes |
|---|---|---|---|---|
scanId | no | scan ID | latest completed scan | Pin it when paging. If omitted, each page re-resolves "latest" |
entityTypes | no | JSON array or CSV of managementGroup, subscription, resourceGroup, resource | all | Unknown values just match nothing |
loadProperties | no | true | off | Adds each item's full Azure properties object |
includeCostData | no | true | off | Adds costData to resource items |
pageSize | no | 1-1000 | 100 for API callers | |
continuationToken | no | token from the previous page | Send exactly as returned (URL-encoded) | |
viewMode | no | any string | Echoed back only |
Errors
- 400
'entityTypes' must be a JSON array of non-empty strings or a comma-separated list. - 400
'entityTypes' must contain at least one entity type. - 400
'pageSize' must be an integer between 1 and 1000. - 500
Failed to retrieve scan results.(unknown scan, no completed scans, or a malformed continuationToken)
Response
| Field | Meaning |
|---|---|
manifest | The scan's full summary record, sent on every page. Useful fields: scanId, status, scanTimestamp, completedTimestamp, totalResources, entitySummary, tenantId, triggeredBy, statusMessage, warnings[], changeSummary (added/deleted/modified counts vs. the previous scan). Many more internal fields; ignore them |
resources[] | One item per entity (below) |
resourceCount | Items in this page |
loadedProperties | Whether properties was included |
pagination | { pageSize, continuationToken?, hasMore }; continuationToken is absent on the last page |
viewMetadata | { viewMode, entityTypes } when sent |
message | Present for empty, failed or cancelled scans |
resources[] item
| Field | Meaning |
|---|---|
id | Lower-case ARM ID |
name, type, location?, resourceGroup? | Identity |
subscriptionId, subscriptionName, tenantId | Owning subscription and tenant |
tags | Object; {} when none |
entityType | managementGroup, subscription, resourceGroup, resource |
rbacSummary | { hasDirectAssignments, directAssignmentCount, eligibleAssignmentCount? } |
rbacAssignments | { collectionStatus: success | failed | insufficient_permissions, directAssignments[], eligibleAssignments[]?, summary, errorDetails? }. Each assignment: { roleDefinitionId, roleDefinitionName, principalId, principalType, principalDisplayName?, scope, roleAssignmentId, createdOn?, updatedOn?, pimAssignmentType?, pimStartDateTime?, pimEndDateTime?, pimMemberType? } |
properties | With loadProperties=true only |
costData | With includeCostData=true, resources only: { hasData, costSnapshot?, lastUpdated? } |
Notes
- Pages may hold fewer rows than
pageSize; keep going whilehasMoreis true. - Child resources (e.g. VM extensions) are not included for completed scans; use
getChildResources. - Failed or cancelled scans return all partial rows in one response with no paging, and
entityTypesis not applied. - A user scoped to subscriptions sees no management-group rows.
Example
GET /api/getScanResults?scanId=…&entityTypes=resource&pageSize=500 then append &continuationToken=<url-encoded token>GET /api/searchResources
Permission: depends on destination: explorer and performance need View Resources; costExplorer View Costs; defender View Defender Assessments; advisor View Advisor Recommendations; policyCompliance View Policy Data
Find entities by name.
Parameters
| Name | Required | Values | Default |
|---|---|---|---|
query | yes | At least 2 characters; case-insensitive substring of the name | |
mode | no | current, all | current |
scanId | no | scan ID | latest completed scan (current mode) |
destination | no | explorer, costExplorer, defender, advisor, policyCompliance, performance | explorer |
entityType | no | all, subscription, resourceGroup, resource | all |
Errors
- 400
destination must be one of explorer, costExplorer, defender, advisor, policyCompliance, performance - 400
entityType must be one of all, subscription, resourceGroup, resource - 403
You do not have permission to search <destination>. - 400
Query parameter 'query' is required. - 400
Query must be at least 2 characters long. - 400
Mode must be 'current' or 'all'. - 500
Failed to execute search.(also for an unknown scan)
Response: { results[], totalCount, searchMode, currentScanId?, query }. Each result:
| Field | Meaning |
|---|---|
resourceId | Lower-case ARM ID |
resourceName, resourceType | Identity |
subscriptionId, subscriptionName | Owning subscription |
resourceGroup | "" when not applicable |
entityType | Entity level |
mostRecentScanId, mostRecentScanTimestamp | In mode=all, the most recent scan holding the entity. In mode=current, mostRecentScanId is the selected scan and the timestamp is the request time. For costExplorer and performance, mostRecentScanId is an empty string, the timestamp is the request time, and existsInCurrentScan is always true |
existsInCurrentScan | Whether the entity is in the selected scan |
latestCostMonth | YYYY-MM, costExplorer only |
Notes
- At most 50 results, with no paging;
totalCountis the number returned. If you get 50, refine the query. mode=allwith explorer can list the same entity more than once (one row per stored version).- Results are ordered by level (management group, subscription, resource group, resource), then by name. With
mode=allanddestination=explorerthe second key is most recent scan, newest first. destination=performanceignoresmodeandscanId;searchModein the response is alwayscurrent.destination=costExplorerwithmode=currentmeans the entity has cost data for the current calendar month, not that it is in the selected scan;scanIdis ignored.
Example
GET /api/searchResources?query=sqlprod&destination=explorer&entityType=resourceGET /api/getEntityDetails
Permission: View Resources (extra insight sections need their own permissions)
Full details of one entity in one scan, with top insights.
Parameters
| Name | Required | Notes |
|---|---|---|
scanId | yes | |
entityId | yes | Full ARM ID, any case |
entityType | no | managementGroup, subscription, resourceGroup, resource. Send entityType=managementGroup for management groups if your scope is restricted, or you get a 404 |
Errors
- 400
Please provide both 'scanId' and 'entityId' query parameters. - 400
entityType must be one of: 'managementGroup', 'subscription', 'resourceGroup', 'resource'. - 404
Entity with ID '<id>' not found in scan '<scanId>'.(also out of scope) - 500
An error occurred while fetching entity details: Scan not found: <scanId>(unknown scan)
Response: { entity, entityType }. The entity fields:
| Field | Meaning |
|---|---|
id, name, type, location?, resourceGroup? | Identity |
subscriptionId, subscriptionName, tenantId | Owning subscription and tenant |
tags, entityType, properties | Tags, level, full Azure properties |
rbacSummary, rbacAssignments | As in getScanResults |
_metadata | { firstScanTimestamp, lastScanTimestamp, firstScanSequence, lastScanSequence }: when this version of the entity first and last appeared |
defenderInsights? | With Defender permission: top 5 unhealthy assessments |
advisorInsights? | With Advisor permission: top 5 recommendations |
costInsights? | With Cost permission: { hasData, costSnapshot: { monthToDateCost, averageDailyCost, previousMonthCost }, recentDailyCosts?: [{ date, cost }], lastUpdated, currency? } |
policyAssignmentsInsights?, policyComplianceInsights? | With Policy permission: top 5 each |
Notes
- An insight section is absent when there is no data, or when it could not be loaded.
- Management groups never get insight sections (
defenderInsights,advisorInsights,costInsights, policy insights), whatever permissions the caller holds.
Example
GET /api/getEntityDetails?scanId=…&entityType=resource&entityId=%2Fsubscriptions%2F<sub>%2FresourceGroups%2Frg-app%2Fproviders%2FMicrosoft.Web%2Fsites%2Fapp1GET /api/getEntityHistory
Permission: View Resources
One entity's state across every completed scan. A heavy call: one full copy of the entity per scan, with no paging.
Parameters
| Name | Required | Notes |
|---|---|---|
entityId | yes | Full ARM ID |
entityType | no | Same values and management-group note as getEntityDetails |
Errors
- 400
Please provide an 'entityId' query parameter. - 400entityType message as in getEntityDetails
- 404
This resource has not been discovered by any scans yet. …(also out of scope) - 500
Failed to retrieve entity history: <detail>
Response: { entityId, entityType, totalScansWithEntity, historyLength, history[] }. history[] is oldest first; each entry:
| Field | Meaning |
|---|---|
scanId, scanTimestamp | The scan |
entityProcessedTimestamp | Null on deleted entries |
status | created, present, deleted |
entityType | Level |
entity | Null when deleted; otherwise { id, name, type, location, resourceGroup, subscriptionId, subscriptionName, tenantId, tags, entityType, rbacAssignments, properties } |
hasChildChanges? | Whether child resources changed |
Notes
presentdoes not mean "unchanged" or "changed". Compare consecutive entries yourself.- A gap between appearances inserts a
deletedentry, and the next appearance iscreated.
Example
GET /api/getEntityHistory?entityType=resource&entityId=…GET /api/getChildResources
Permission: View Resources
Child resources of a resource (e.g. VM extensions), optionally compared with an older scan.
Parameters
| Name | Required | Notes |
|---|---|---|
parentEntityId | yes | ARM ID |
scanId | yes | |
olderScanId | no | Turns on comparison |
Errors
- 400
Please provide both 'parentEntityId' and 'scanId' query parameters. - 500
Scan not found: <id>
Response: { children[], hasChildrenWithChanges }. Each child:
| Field | Meaning |
|---|---|
id | Internal record ID, not the ARM ID |
entityId | Lower-case ARM ID |
name, type, location?, properties, tags? | The child |
changeStatus | Comparison mode only: added, modified, deleted, unchanged |
diffTree | Only when modified: a nested before/after tree of property changes |
Notes
- An out-of-scope parent returns 200 with an empty list.
- Children are sorted by name in comparison mode only.
- The child diff does not apply ignore rules.
Example
GET /api/getChildResources?scanId=…&olderScanId=…&parentEntityId=%2Fsubscriptions%2F…%2Fvirtualmachines%2Fvm1Role Assignments
Three terms these routes use:
- Inherited
- Received through Entra group membership. It does not mean inherited down the scope tree.
- Role category
- Derived from the role name. critical: contains "owner", "user access administrator" or "administrator". management: contains "contributor", "manager" or "operator". read: contains "reader", "viewer" or "monitoring". Otherwise other.
- PIM status
permanent,active(activated through PIM),eligible.
Known limitation
These routes drop rows inherited through an Entra group when that group's membership lookup fails. The failure is logged server-side only.
GET /api/role-assignments-summary
Permission: View Resources
Role assignments per principal (user, group, service principal) in a scan.
Parameters
| Name | Required | Notes |
|---|---|---|
scanId | yes |
Errors
- 400
scanId is required - 500
Failed to retrieve role assignments summary(also for an unknown scan)
Response: { compactSummaries[], availableFilters, totalCount }. availableFilters is { principalTypes[], roleCategories[], scopeTypes[], roleNames[] }; totalCount is the total number of assignments, not principals. Each summary:
| Field | Meaning |
|---|---|
principalId, principalDisplayName, principalType | User, Group, ServicePrincipal |
totalAssignments | Direct + inherited |
criticalRoleCount, managementRoleCount, readRoleCount, otherRoleCount | Counts per role category |
roleCategoryCounts, scopeTypeCounts | Dictionaries |
directAssignmentCount, inheritedAssignmentCount | Counts per source |
combinedCounts | Key category|scopeType |
sourceAwareCombinedCounts | Key source|pimStatus|category|scopeType|roleName. The role name is last and may itself contain | |
Example
GET /api/role-assignments-summary?scanId=…GET /api/role-assignments-details
Permission: View Resources
Every assignment for one principal, with filters. Filters are encoded lists.
Parameters
| Name | Required | Values |
|---|---|---|
scanId | yes | |
principalId | yes | Exact match |
roleCategoryFilter | no | Encoded list of critical, management, read, other |
scopeTypeFilter | no | Encoded list of managementGroup, subscription, resourceGroup, resource |
assignmentSourceFilter | no | Encoded list of direct, inherited |
pimStatusFilter | no | Encoded list of permanent, eligible, active |
roleNameFilter | no | Encoded list of exact role names (encode twice) |
Errors
- 400
scanId is required - 400
principalId is required - 400
<name> values must be one of <values> - 400
<name> contains a value that is not valid percent-encoding - 400
<name> must list at most 200 values - 500
Failed to retrieve role assignments details
Response: { assignments[], totalCount }. An unknown principal returns an empty list. Each assignment:
| Field | Meaning |
|---|---|
roleDefinitionName, roleDefinitionId, roleCategory | The role |
scope | ARM scope path; use this, not entityId |
entityId | Internal record ID of the scope entity |
entityName, entityType | Scope entity name and level |
subscriptionId?, subscriptionName?, resourceGroup? | Location of the scope |
createdOn, updatedOn? | Dates |
roleAssignmentId | Assignment ID |
assignmentSource | direct, inherited |
grantedViaGroupId?, grantedViaGroupName? | On inherited rows |
pimStatus, pimAssignmentType? (Activated | Assigned), pimStartDateTime?, pimEndDateTime?, pimMemberType? | PIM details |
Example
GET /api/role-assignments-details?scanId=…&principalId=<guid>&roleCategoryFilter=critical,management&pimStatusFilter=eligiblePerformance Metrics
GET /api/performance-metrics-summary
Permission: View Resources
Which resources have performance metrics, and which metrics. No parameters. No metric values are included; use the details route for values.
Response: { compactSummaries[], availableMetrics[], availableResourceTypes[] }. Each summary:
| Field | Meaning |
|---|---|
entityId, displayName, entityType | Lower-case resource ID, name, always resource |
subscriptionId, subscriptionName, resourceGroupName, resourceType | Location and type |
location?, tags? | Optional |
totalMetricDays, totalMetricHours | How much data exists |
collectionWindow | { daily: { startDate, endDate, totalDays, collectionTimestamp }, hourly?: { … } } |
availableMetrics[] | Metric names collected for this resource |
Errors
- 500
Failed to retrieve performance metrics summary
Example
GET /api/performance-metrics-summaryGET /api/performance-metrics-details
Permission: View Resources
All collected metric values for one resource. The payload can be large.
Parameters
| Name | Required | Notes |
|---|---|---|
subscriptionId | yes | Must match exactly as returned by the summary |
resourceGroupName | yes | |
resourceName | yes |
Errors
- 400
subscriptionId, resourceGroupName, and resourceName are required - 404
No performance metrics found for specified resource(also when the subscription is out of scope) - 500
Failed to retrieve performance metrics details
Response: { resource, skuMemoryBytes, vmSize }.
| Field | Meaning |
|---|---|
resource.resourceId, originalResourceId, resourceName, resourceType, resourceGroupName, subscriptionId, subscriptionName | Identity |
resource.collectionWindow | As in the summary |
resource.dailyMetrics[] | About 365 days: { date, metrics: { <metricName>: { average?, maximum?, minimum?, total? } } } |
resource.hourlyMetrics?[] | About 30 days: { timestamp, metrics } |
skuMemoryBytes, vmSize | VMs and scale sets only (memory per instance); otherwise null |
Example
GET /api/performance-metrics-details?subscriptionId=<guid>&resourceGroupName=rg-prod&resourceName=vm-web-01