Building custom solutions that extend, automate, and integrate Microsoft 365 apps.
Based on my research, the client credentials flow can be used to connect Microsoft Defender data to Power BI. However, successfully obtaining an access token only confirms that the application was authenticated. It does not confirm that the token was issued for the correct API resource or contains the application roles required by the endpoint.
The first thing I would verify is the exact endpoint being called.
- Confirm whether you need the Machines API or the DeviceInfo table
For Microsoft Defender for Endpoint device inventory, the documented endpoint is:
GET https://api.security.microsoft.com/api/machines
This endpoint supports application authentication and requires one of the following WindowsDefenderATP application permissions: Machine.Read.All
or, if write access is required: Machine.ReadWrite.All
These permissions are exposed by the WindowsDefenderATP, also shown as Microsoft Defender for Endpoint, enterprise application. They are not Microsoft Graph permissions. Microsoft documents Machine.Read.All as the application permission for reading all machine profiles.
A Power BI reporting implementation using an Entra ID application and Machine.Read.All has also been reported as working when the permission is added under the WindowsDefenderATP API.
However, this endpoint is not commonly documented: GET https://api.security.microsoft.com/api/deviceinfo
DeviceInfo is normally an Advanced Hunting table, not a standalone REST resource. If the goal is to query the DeviceInfo table, you need to run a KQL query through an Advanced Hunting endpoint rather than calling /api/deviceinfo directly.
For example, using Microsoft Graph: POST https://graph.microsoft.com/v1.0/security/runHuntingQuery
Request body:
{
"Query": "DeviceInfo | summarize arg_max(Timestamp, *) by DeviceId | take 1000"
}
For app-only authentication, this Microsoft Graph operation requires the following application permission: ThreatHunting.Read.All
Microsoft Graph documents ThreatHunting.Read.All as the least-privileged application permission for runHuntingQuery.
Therefore, the correct permission depends on what you are trying to retrieve:
- For current Defender machine profiles through
/api/machines, use the WindowsDefenderATP application permissionMachine.Read.All. - For the Advanced Hunting
DeviceInfotable, use Microsoft GraphThreatHunting.Read.Alland call/security/runHuntingQuery.
Ref:
2.Verify the token scope and audience
For the Defender API, the OAuth 2.0 client credentials token request should normally use:
scope=https://api.security.microsoft.com/.default
For Microsoft Graph Advanced Hunting, use:
scope=https://graph.microsoft.com/.default
A token issued for Microsoft Graph cannot be used against api.security.microsoft.com, and a Defender token cannot be used against Microsoft Graph.
It is possible to receive a valid token that does not contain the expected application permissions if the token was requested for the wrong resource. Similar Defender API authorization issues have been resolved by correcting the resource or scope used in the token request.
3.Check Defender licensing, provisioning, and device availability
Also verify that:
- Microsoft Defender for Endpoint is provisioned for the tenant.
- The tenant has an applicable Defender license.
- Devices have been onboarded and are communicating with Defender.
- Admin consent has been granted for the application permission.
- The application is accessing the correct tenant.
- Any Defender RBAC or device-group configuration is not limiting access.
For the documented Machines API, Microsoft states that results are subject to Defender device-group access and that the API returns machines that have communicated with Defender for Endpoint.
- Power Query example for the Machines API
If the requirement is to retrieve the Defender device inventory, the Power Query request can follow this pattern:
let
TenantId = "YOUR-TENANT-ID",
ClientId = "YOUR-CLIENT-ID",
ClientSecret = "YOUR-CLIENT-SECRET",
TokenResponse =
Json.Document(
Web.Contents(
"https://login.microsoftonline.com",
[
RelativePath = TenantId & "/oauth2/v2.0/token",
Headers = [
#"Content-Type" = "application/x-www-form-urlencoded"
],
Content =
Text.ToBinary(
Uri.BuildQueryString(
[
client_id = ClientId,
client_secret = ClientSecret,
scope = "https://api.security.microsoft.com/.default",
grant_type = "client_credentials"
]
)
)
]
)
),
AccessToken = TokenResponse[access_token],
DefenderResponse =
Json.Document(
Web.Contents(
"https://api.security.microsoft.com",
[
RelativePath = "api/machines",
Headers = [
Authorization = "Bearer " & AccessToken,
Accept = "application/json"
]
]
)
),
Machines = DefenderResponse[value],
Result =
if List.Count(Machines) = 0 then
#table({}, {})
else
Table.FromRecords(Machines)
in
Result
The documented Machines API supports GET /api/machines, uses a bearer token, and supports OData operators such as $filter, $top, and $skip.
In Power BI, because the bearer token is being supplied by Power Query, the credentials for the API data source usually need to be configured as Anonymous. The query itself will still send the Authorization: Bearer header.
I hope this information helps.