Microsoft is retiring the Microsoft Defender for Endpoint and Microsoft Defender XDR advanced hunting APIs: according to Message center post MC1220762, they no longer function after 1 February 2027, and you should finish migrating by 31 January 2027. The replacement is the Microsoft Graph security API action runHuntingQuery, called at https://graph.microsoft.com/v1.0/security/runHuntingQuery with the ThreatHunting.Read.All permission. Your KQL queries normally carry over unchanged; the token audience, endpoint, permission and response property names are what you change.
Who this is for and what you will have
This guide is for security engineers and developers who run advanced hunting queries from scripts, SIEM or SOAR integrations, Power Automate flows, Logic Apps or Power BI reports. At the end you will have:
- A list of integrations and app registrations still calling the old endpoints.
- The new permission granted and the old ones scheduled for removal.
- Working code against
runHuntingQuery, with a side-by-side comparison of the old and new calls. - A test plan to run both in parallel before the cutoff.
What changes and when
| Date | Event |
|---|---|
| January 2026 | Retirement begins, per the Defender for Endpoint API page. |
| 22 January 2026 | MC1220762 published, with retirement starting 6 February 2026. |
| 26 October 2026 | MC1220762 updated as a reminder. |
| 31 January 2027 | Act-by date: migrate every workflow. |
| 1 February 2027 | The old APIs stop functioning and stop returning data. |
Microsoft sent MC1220762 to tenants where its telemetry suggests the old APIs are in use, so if you received it, treat that as a strong signal that something in your tenant still calls them.
Which endpoints are retiring
| API | Endpoint | Old permissions |
|---|---|---|
| Defender for Endpoint advanced hunting | POST https://api.securitycenter.microsoft.com/api/advancedqueries/run, also documented as https://api.security.microsoft.com/api/advancedqueries/run and regional hosts such as us.api.security.microsoft.com | AdvancedQuery.Read (delegated), AdvancedQuery.Read.All (application) on the Microsoft Defender for Endpoint API (formerly Windows Defender Advanced Threat Protection) |
| Defender XDR advanced hunting | POST https://api.security.microsoft.com/api/advancedhunting/run | AdvancedHunting.Read (delegated), AdvancedHunting.Read.All (application) on Microsoft Threat Protection |
A related deadline: the Microsoft Graph legacy alerts API (/security/alerts) was due for retirement on 15 October 2026, with alerts_v2 and incidents as the replacement. If your integration still uses it, it's already on borrowed time.
What the new API looks like
| Subject | Old API | Microsoft Graph |
|---|---|---|
| Endpoint | .../api/advancedqueries/run or .../api/advancedhunting/run | https://graph.microsoft.com/v1.0/security/runHuntingQuery |
| Token audience | Defender for Endpoint resource, https://api.securitycenter.microsoft.com | Microsoft Graph, https://graph.microsoft.com |
| Permission | AdvancedQuery.Read(.All) or AdvancedHunting.Read(.All) | ThreatHunting.Read.All, delegated or application |
| Request body | Query | Query, optional Timespan (ISO 8601) and optional workspaceId |
| Response | Stats, Schema, Results | huntingQueryResults with schema and results |
runHuntingQuery is available in Microsoft Graph v1.0 for the global service, US Government L4 and US Government L5 (DOD). It is not available in Microsoft Graph China operated by 21Vianet, and Microsoft doesn't document an alternative for that cloud on the pages reviewed here.
Limits
Microsoft documents these quotas for Graph advanced hunting:
- Queries return data from the past 30 days.
- Up to 100,000 rows per result and an overall result size limit of 50 MB.
- At least 45 calls per minute per tenant, varying with tenant size.
- CPU is allocated per tenant; at 100 percent, queries are blocked until the next 15-minute cycle.
- A single request times out after three minutes.
- HTTP
429means you reached a call or CPU limit; the response body says which.
The old API documented a 200-second maximum per request and fixed hourly and daily execution budgets, so retry logic tuned for those numbers should be reviewed.
One inconsistency to know about: the runHuntingQuery page shows an example with "Timespan": "P90D", while the quota list says queries explore the past 30 days. Microsoft's documentation doesn't reconcile the two, so test the time range you need rather than assuming more than 30 days.
Prerequisites
- An app registration in Microsoft Entra ID for each integration, or permission to create one.
- An admin who can grant tenant-wide consent to Microsoft Graph application permissions; Microsoft's admin consent documentation requires Privileged Role Administrator for Microsoft Graph app roles.
- For delegated calls, users with access to the advanced hunting data in Microsoft Defender XDR.
- Microsoft Graph PowerShell (
Microsoft.Graph.Security) if you want to use the SDK cmdlet.
Step 1: Find everything that calls the old APIs
Search code and configuration for these strings: advancedqueries/run, advancedhunting/run, api.securitycenter.microsoft.com and api.security.microsoft.com. Include SIEM connector settings, SOAR playbooks, Azure Functions, Logic Apps definitions, Power BI data source queries and scheduled scripts.
Check Power Automate and Logic Apps for the Advanced Hunting action in the Microsoft Defender ATP connector.
Check app registrations for the old permissions. This script lists which service principals hold application permissions on the two old APIs. It uses standard Microsoft Graph PowerShell cmdlets; it isn't a script Microsoft publishes:
Connect-MgGraph -Scopes 'Application.Read.All'
foreach ($api in 'WindowsDefenderATP', 'Microsoft Threat Protection') {
$sp = Get-MgServicePrincipal -Filter "displayName eq '$api'"
if (-not $sp) { continue }
Get-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $sp.Id -All |
ForEach-Object {
$role = $sp.AppRoles | Where-Object Id -eq $_.AppRoleId
[pscustomobject]@{
Api = $api
ClientApp = $_.PrincipalDisplayName
Permission = $role.Value
}
} | Where-Object Permission -in 'AdvancedQuery.Read.All', 'AdvancedHunting.Read.All'
}Every client app in the output is a candidate for migration. Delegated permissions (AdvancedQuery.Read, AdvancedHunting.Read) are granted differently and won't appear here; review those under each app's API permissions page in the Microsoft Entra admin center.
Step 2: Grant the Graph permission
For each app that needs to keep hunting:
- In the Microsoft Entra admin center, open App registrations and select the app.
- Select API permissions > Add a permission > Microsoft Graph.
- Choose Application permissions for a daemon or Delegated permissions for a signed-in user, then select ThreatHunting.Read.All and Add permissions.
- Select Grant admin consent for your organization.
Keep the old permission until the new code is tested in production; remove it afterwards.
Step 3: Change the code
Before: Defender for Endpoint API
A typical legacy daemon script requests a token for the Defender for Endpoint resource and posts the query:
$tenantId = '<tenant-id>'
$appId = '<app-id>'
$secret = '<client-secret>'
$tokenBody = @{
resource = 'https://api.securitycenter.microsoft.com'
client_id = $appId
client_secret = $secret
grant_type = 'client_credentials'
}
$token = (Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$tenantId/oauth2/token" -Body $tokenBody).access_token
$body = @{ Query = "DeviceProcessEvents | where InitiatingProcessFileName =~ 'powershell.exe' | project Timestamp, FileName, InitiatingProcessFileName, DeviceId | limit 2" } | ConvertTo-Json
$response = Invoke-RestMethod -Method Post -Uri 'https://api.securitycenter.microsoft.com/api/advancedqueries/run' -Headers @{ Authorization = "Bearer $token" } -ContentType 'application/json' -Body $body
$response.ResultsAfter: Microsoft Graph with the PowerShell SDK
The SDK cmdlet that wraps runHuntingQuery is Start-MgSecurityHuntingQuery. With app-only authentication and a certificate:
Import-Module Microsoft.Graph.Security
Connect-MgGraph -TenantId '<tenant-id>' -ClientId '<app-id>' -CertificateThumbprint '<thumbprint>'
$params = @{
Query = "DeviceProcessEvents | where InitiatingProcessFileName =~ 'powershell.exe' | project Timestamp, FileName, InitiatingProcessFileName, DeviceId | limit 2"
Timespan = 'P7D'
}
$result = Start-MgSecurityHuntingQuery -BodyParameter $params
$result.ResultsAfter: Microsoft Graph over REST
If you call the REST API directly, request a token for Microsoft Graph with the client credentials flow and the .default scope, then post to the new endpoint:
$tokenBody = @{
client_id = $appId
scope = 'https://graph.microsoft.com/.default'
client_secret = $secret
grant_type = 'client_credentials'
}
$token = (Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Body $tokenBody).access_token
$body = @{
Query = "DeviceProcessEvents | where InitiatingProcessFileName =~ 'powershell.exe' | project Timestamp, FileName, InitiatingProcessFileName, DeviceId | limit 2"
Timespan = 'P7D'
} | ConvertTo-Json
$response = Invoke-RestMethod -Method Post -Uri 'https://graph.microsoft.com/v1.0/security/runHuntingQuery' -Headers @{ Authorization = "Bearer $token" } -ContentType 'application/json; charset=utf-8' -Body $body
$response.resultsPrefer a certificate over a client secret for production daemons. Microsoft notes that if a query contains non-ANSI characters, such as lookalike characters in email subjects, the Content-Type should be application/json; charset=utf-8.
The response
The documented Graph response looks like this:
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#microsoft.graph.security.huntingQueryResults",
"schema": [
{ "name": "Timestamp", "type": "DateTime" },
{ "name": "FileName", "type": "String" },
{ "name": "InitiatingProcessFileName", "type": "String" }
],
"results": [
{
"Timestamp": "2024-03-26T09:39:50.7688641Z",
"FileName": "cmd.exe",
"InitiatingProcessFileName": "powershell.exe"
}
]
}Property access in PowerShell is case-insensitive, so $response.Results still works. In Python dictionaries, JavaScript objects and strict JSON deserializers it doesn't: change Results to results, Schema to schema, and Name/Type inside the schema to name/type. Code that read Stats from the old response has no equivalent property in the documented Graph response. Microsoft's second runHuntingQuery example also shows result columns in camelCase (timestamp, fileName), so don't hard-code the casing of column names; read them from schema.
Step 4: Power Platform and Power BI
- Power Automate, Power Apps and Logic Apps: Microsoft says Graph has no built-in equivalent of the Defender ATP connector's Advanced Hunting action. Create a custom connector that calls
runHuntingQuery, following Microsoft's Power Automate custom connector tutorial for Microsoft Graph, and use the parameters in the table above. - Power BI: update the report's query to the Graph endpoint, token audience and body. Microsoft has a separate article on custom Defender XDR reports with the Graph security API and Power BI.
Step 5: Test, cut over and roll back
- Run the old and new calls side by side with the same query and a fixed time range, and compare row counts and a sample of rows.
- Load test at your real schedule. Look for
429responses and three-minute timeouts, and add back-off that reads the response body. - Switch production to the new call. Keep the old code path behind a configuration flag until you have a few days of clean runs.
- Rollback before 1 February 2027 is that flag: the old API still answers until then. After that date there is nothing to roll back to, so finish the cutover with time to spare.
- Remove
AdvancedQuery.Read.AllorAdvancedHunting.Read.Allfrom each migrated app and revoke any client secrets you replaced with certificates.
Troubleshooting
401 or 403 from Graph. The token is still for the old resource, or ThreatHunting.Read.All hasn't been consented. Decode the token and check the audience is Microsoft Graph and the roles claim includes the permission.
A large query fails after the switch. The old API returned 400 Bad Request for results over 50 MB, and Graph documents the same 50 MB overall limit, along with a three-minute timeout. Reduce columns and rows with project, summarize and limit.
Parser errors after the switch. The code expects Results and Schema with capital letters. Update it as described above.
Fewer rows than expected. Graph applies the shorter of the query's own time filter and Timespan. If you didn't set Timespan, the default is 30 days.
For KQL you can reuse in these calls, see Defender XDR advanced hunting KQL queries for identity attacks.
Checklist
- Searched code, connectors, flows and reports for the old endpoints.
- Listed app registrations with
AdvancedQuery.Read.AllorAdvancedHunting.Read.All. - Granted
ThreatHunting.Read.Alland admin consent. - Updated token audience, endpoint, body and response parsing.
- Rebuilt Power Automate actions as a custom connector and updated Power BI queries.
- Ran old and new side by side, then cut over before 31 January 2027.
- Removed the old permissions and secrets.
What we checked
- Opened MC1220762 on mc.merill.net on 2026-11-12: published 22 January 2026, updated 26 October 2026, retirement starting 6 February 2026, full retirement 1 February 2027 and act-by 31 January 2027. The post itself lists no endpoint URLs or permissions.
- Copied the migration table (endpoints, resource URI, old and new permissions, request body and response properties) from the Microsoft Graph security API overview, updated 3 November 2026, which says the older APIs stop returning data on 1 February 2027.
- Confirmed on the runHuntingQuery v1.0 page that the least privileged permission is
ThreatHunting.Read.Allfor both delegated and application, that 21Vianet isn't supported, and copied theStart-MgSecurityHuntingQuerycmdlet and the documented response. - Compared the limits on the old Defender for Endpoint API page (updated 28 July 2026: 200 seconds per request, 45 calls per minute, 1,500 per hour) with the Graph quotas (three-minute timeout, CPU per 15-minute cycle).
- Noted two documentation inconsistencies: the Defender for Endpoint page says retirement began in January 2026 while MC1220762 says 6 February 2026, and the runHuntingQuery example uses
P90Dwhile the quotas say 30 days. - The app registration script in Step 1 is our own composition of documented Microsoft Graph PowerShell cmdlets and wasn't run against a tenant. The "before" token request follows the client credentials pattern the old Defender for Endpoint samples used.
- Not tested on a live tenant; steps follow Microsoft's documentation as of 2026-11-12.
Last tested: 2026-11-12
References
- MC1220762, Retirement notice: MDE and XDR advanced hunting APIs retiring (Message center mirror): https://mc.merill.net/message/MC1220762
- Use the Microsoft Graph security API (advanced hunting and migration): https://learn.microsoft.com/en-us/graph/api/resources/security-api-overview?view=graph-rest-1.0
- security: runHuntingQuery (v1.0): https://learn.microsoft.com/en-us/graph/api/security-security-runhuntingquery?view=graph-rest-1.0
- Advanced hunting API, Microsoft Defender for Endpoint: https://learn.microsoft.com/en-us/defender-endpoint/api/run-advanced-query-api
- Defender XDR advanced hunting API: https://learn.microsoft.com/en-us/defender-xdr/api-advanced-hunting
- Get access without a user (client credentials): https://learn.microsoft.com/en-us/graph/auth-v2-service
- Create custom Microsoft Defender XDR reports using Microsoft Graph security API and Power BI: https://learn.microsoft.com/en-us/defender-xdr/defender-xdr-custom-reports
Comments
Questions and tips are welcome. Comments are checked by the EpiFive team before they appear; please do not share passwords, phone numbers or other private details.