# Defender advanced hunting API retirement: move to Graph runHuntingQuery

> The Defender for Endpoint and Defender XDR advanced hunting APIs stop working on 1 February 2027 (MC1220762). Find callers, switch to Microsoft Graph runHuntingQuery and ThreatHunting.Read.All.

Source: https://www.epifive.com/blog/defender-advanced-hunting-api-retirement-graph-runhuntingquery · Published 2026-11-12 · EpiFive

## Key takeaways

- The Defender for Endpoint and Defender XDR advanced hunting APIs stop functioning on 1 February 2027, and Microsoft asks for migration by 31 January 2027 (MC1220762).
- The replacement is POST https://graph.microsoft.com/v1.0/security/runHuntingQuery with the ThreatHunting.Read.All permission.
- Existing KQL usually works unchanged, but the token audience, endpoint and lowercase schema and results properties change.
- Power Automate flows that used the Defender ATP connector's Advanced Hunting action need a custom connector to Microsoft Graph.
- runHuntingQuery isn't available in Microsoft Graph China operated by 21Vianet.

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 `429` means 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:

```powershell
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:

1. In the Microsoft Entra admin center, open **App registrations** and select the app.
2. Select **API permissions** > **Add a permission** > **Microsoft Graph**.
3. Choose **Application permissions** for a daemon or **Delegated permissions** for a signed-in user, then select **ThreatHunting.Read.All** and **Add permissions**.
4. 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:

```powershell
$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.Results
```

### After: Microsoft Graph with the PowerShell SDK

The SDK cmdlet that wraps `runHuntingQuery` is `Start-MgSecurityHuntingQuery`. With app-only authentication and a certificate:

```powershell
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.Results
```

### After: 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:

```powershell
$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.results
```

Prefer 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:

```json
{
    "@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

1. 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.
2. Load test at your real schedule. Look for `429` responses and three-minute timeouts, and add back-off that reads the response body.
3. Switch production to the new call. Keep the old code path behind a configuration flag until you have a few days of clean runs.
4. 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.
5. Remove `AdvancedQuery.Read.All` or `AdvancedHunting.Read.All` from 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](https://www.epifive.com/blog/advanced-hunting-kql-identity-attacks).

## Checklist

- Searched code, connectors, flows and reports for the old endpoints.
- Listed app registrations with `AdvancedQuery.Read.All` or `AdvancedHunting.Read.All`.
- Granted `ThreatHunting.Read.All` and 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.All` for both delegated and application, that 21Vianet isn't supported, and copied the `Start-MgSecurityHuntingQuery` cmdlet 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 `P90D` while 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

## Frequently asked questions

### When do the Defender advanced hunting APIs stop working?

Message center post MC1220762 says the Microsoft Defender for Endpoint and Defender XDR advanced hunting APIs are fully retired on 1 February 2027 and will no longer function after that date. Microsoft asks you to finish migrating by 31 January 2027. There is no automatic migration.

### Which permission does Microsoft Graph runHuntingQuery need?

ThreatHunting.Read.All, as a delegated or application permission on Microsoft Graph. It replaces AdvancedQuery.Read and AdvancedQuery.Read.All on the Defender for Endpoint API and AdvancedHunting.Read and AdvancedHunting.Read.All on Microsoft Threat Protection.

### Do I need to rewrite my KQL queries?

Usually not. runHuntingQuery takes the same Kusto query in the Query property. What changes is the endpoint, the token audience, the permission and the response property names, which are schema and results in lowercase instead of Schema and Results.

### Is there a Power Automate action for Graph advanced hunting?

Not a built-in one. Microsoft says Graph has no equivalent of the Advanced Hunting action in the Microsoft Defender ATP connector, and recommends a custom connector that calls runHuntingQuery.

### Is runHuntingQuery available in all clouds?

Microsoft lists it for the global service, US Government L4 and US Government L5 (DOD), but not for Microsoft Graph China operated by 21Vianet.

