AZURE / IDENTITY GOVERNANCE / MIGRATION DEADLINE
Microsoft Entra Privileged Identity Management (PIM) Iteration 2 beta APIs stop returning data on October 28, 2026. Any job still calling the legacy /beta/privilegedAccess surface can fail at the exact point where teams expect it to enumerate, activate, or govern privileged access. This guide turns the deadline into an inventory, migration, and evidence plan.
Executive checkpoint
- Deadline: October 28, 2026.
- Legacy signal: Microsoft Graph requests containing
/beta/privilegedAccess/aadRolesor/beta/privilegedAccess/azureResources. - Replacement split: Microsoft Graph v1.0 for Microsoft Entra roles and PIM for Groups; Azure Resource Manager (ARM) authorization APIs for Azure resource roles.
- Primary risk: silent gaps in access reviews, role activation workflows, privileged-access reporting, or custom governance automation.
- Do now: find every caller, map each operation, test response parity, cut over, and retain rollback evidence.
What Microsoft is retiring
Microsoft’s Entra release announcement says applications and scripts using PIM Iteration 2 beta APIs for Azure resources, Microsoft Entra roles, and Groups will fail starting October 28. The underlying API history is more precise: the deprecated Microsoft Graph surface lives under /beta/privilegedAccess, with /aadRoles and /azureResources segments for directory roles and Azure resource roles.
This is an API retirement, not a new vulnerability. PIM itself remains available. The operational issue is that a successful sign-in or valid token does not rescue a client that calls a retired route. A scheduled script can authenticate normally and still receive no usable data from its old PIM operation.
Microsoft calls Iteration 3 the current model. It uses generally available APIs for the core assignment scenarios and separates providers by control plane. That separation is the most important architectural change to understand before editing code.
Migration map: the provider determines the API
| Privileged resource | Deprecated pattern | Current control plane | Typical current resources |
|---|---|---|---|
| Microsoft Entra roles | graph.microsoft.com/beta/privilegedAccess/aadRoles/… | Microsoft Graph v1.0 | roleManagement/directory/roleAssignmentSchedule* and roleEligibilitySchedule* |
| Azure resource roles | graph.microsoft.com/beta/privilegedAccess/azureResources/… | Azure Resource Manager | Microsoft.Authorization/roleAssignmentSchedule* and roleEligibilitySchedule* |
| PIM for Groups | Legacy beta integration or wrapper | Microsoft Graph v1.0 | identityGovernance/privilegedAccess/group/… schedule resources |
Do not migrate by replacing only beta with v1.0. Azure resource-role operations move from Microsoft Graph to ARM, which changes the host, resource scope, authorization model, request shape, and often the permissions assigned to the workload identity. Microsoft notes that ARM PIM operations do not require Graph permissions, but the calling user or service principal needs sufficient Azure RBAC authority: at least Owner or User Access Administrator on the resource being administered.
Read models are not request models
Iteration 3 distinguishes schedules, schedule instances, and requests. Use an *AssignmentScheduleRequest or *EligibilityScheduleRequest to create or change an assignment. Read-only schedule objects describe current or future assignments; schedule-instance objects describe assignments that are currently in effect. For example, Microsoft Graph documents GET /v1.0/roleManagement/directory/roleAssignmentScheduleInstances for active Entra role assignment instances.
This difference matters for parity testing. A legacy “list assignments” call might need more than one Iteration 3 query depending on whether the consumer needs active assignments, eligible assignments, future schedules, requests, or PIM-only properties such as an end time.
Where hidden dependencies usually live
- PowerShell modules and scripts checked into operations repositories.
- Azure Automation runbooks, Functions, Logic Apps, and CI/CD jobs.
- Identity-governance tools that build a privileged-role inventory for auditors.
- ServiceNow, SOAR, or ticketing workflows that request or deactivate roles.
- Dashboards that call Graph directly rather than reading a supported export.
- Secrets vault jobs whose app registration has Graph beta permissions.
- SDK wrappers that hide the literal URL from a simple text search.
Repository search is necessary but not sufficient. Combine it with proxy logs, API management telemetry, Azure sign-in logs for workload identities, runbook inventories, and app-registration ownership records. Search configuration stores as well as source code. If a third-party product owns the caller, obtain a vendor statement naming its replacement API and supported release, not merely a promise that it is “Graph compatible.”
Hands-on lab: find PIM Iteration 2 calls safely
This static lab does not connect to Microsoft Graph or an Entra tenant. It scans a local directory for the two documented Iteration 2 URL families and emits JSON that can become a migration backlog or CI artifact.
Prerequisites and isolated workspace
- Python 3.9 or newer.
- A disposable directory containing synthetic files; do not copy production secrets into the lab.
- No network access and no Microsoft credentials are required.
mkdir -p pim-api-audit/sample
cd pim-api-audit
cat > sample/legacy.ps1 <<'EOF'
$uri = 'https://graph.microsoft.com/beta/privilegedAccess/aadRoles/roleAssignments'
Invoke-RestMethod -Method Get -Uri $uri -Headers $headers
EOF
cat > sample/azure.sh <<'EOF'
curl -sS 'https://graph.microsoft.com/beta/privilegedAccess/azureResources/roleAssignments'
EOF
cat > sample/current.txt <<'EOF'
https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignmentScheduleInstances
EOFInventory script
#!/usr/bin/env python3
"""Find Microsoft Entra PIM Iteration 2 endpoint references in text files."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
PATTERN = re.compile(
r"https://graph\.microsoft\.com/beta/privilegedAccess/"
r"(?P<provider>aadRoles|azureResources)(?P<route>[^\s'\"`]*)",
re.IGNORECASE,
)
SKIP_PARTS = {".git", "node_modules", "vendor", ".venv", "venv"}
def scan(root: Path) -> list[dict[str, object]]:
findings: list[dict[str, object]] = []
for path in sorted(root.rglob("*")):
if not path.is_file() or any(part in SKIP_PARTS for part in path.parts):
continue
try:
text = path.read_text(encoding="utf-8")
except (UnicodeDecodeError, OSError):
continue
for line_number, line in enumerate(text.splitlines(), start=1):
for match in PATTERN.finditer(line):
findings.append({
"file": str(path.relative_to(root)),
"line": line_number,
"provider": match.group("provider"),
"endpoint": match.group(0).rstrip(".,);"),
})
return findings
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("root", nargs="?", default=".")
parser.add_argument("--output", default="pim-v2-findings.json")
args = parser.parse_args()
root = Path(args.root).resolve()
findings = scan(root)
Path(args.output).write_text(
json.dumps(findings, indent=2) + "\n", encoding="utf-8"
)
print(f"PIM v2 references: {len(findings)}")
for item in findings:
print(
f"{item['file']}:{item['line']} "
f"[{item['provider']}] {item['endpoint']}"
)
return 2 if findings else 0
if __name__ == "__main__":
raise SystemExit(main())Save the script as pim_v2_inventory.py, then run it:
python3 pim_v2_inventory.py sample --output pim-v2-findings.json
status=$?
printf 'exit=%s\n' "$status"
sed -n '1,120p' pim-v2-findings.jsonExpected console output:
PIM v2 references: 2
azure.sh:1 [azureResources] https://graph.microsoft.com/beta/privilegedAccess/azureResources/roleAssignments
legacy.ps1:1 [aadRoles] https://graph.microsoft.com/beta/privilegedAccess/aadRoles/roleAssignments
exit=2The non-zero exit code is intentional: it lets a CI job fail when deprecated references are found. A zero exit means the scanner found no literal matches, not that the environment is fully migrated. Dynamically assembled URLs, SDK calls, encrypted configuration, and vendor-managed integrations need separate review.
Troubleshooting and cleanup
- If Python reports a syntax error around type hints, use Python 3.9 or later.
- If a known call is missed, inspect whether the hostname or path is assembled from variables; add a repository-specific pattern rather than weakening the global expression blindly.
- If the scan is slow, point it at source and infrastructure directories separately and keep dependency folders excluded.
- Treat JSON findings as potentially sensitive because paths can reveal internal project names.
cd ..
rm -rf pim-api-auditA defensible migration sequence
1. Assign an owner to every caller
Build a register with repository or service name, workload identity, business owner, endpoint family, operation, frequency, downstream consumer, and last observed execution. Tag unknown owners as a migration risk. Deleting an “unused” app registration without runtime evidence can break the only emergency-access report a team has.
2. Map intent, not just endpoints
Record what each call is trying to achieve: list active roles, list eligibilities, submit an activation, remove an assignment, or manage a policy. Then select the matching request, schedule, or schedule-instance resource in the correct provider. Microsoft’s migration table maps Azure role-definition, assignment-request, assignment-list, and policy operations to ARM replacements; use it as the starting point, then compare fields consumed by your code.
3. Rebuild permissions with least privilege
Do not carry broad Graph permissions into the new design automatically. The documented least-privileged application permission for listing Entra active role assignment instances is RoleAssignmentSchedule.Read.Directory. Write operations require different privileges. ARM calls rely on Azure RBAC at a resource scope. Review each workflow separately, document the justification, and remove obsolete grants only after cutover.
4. Prove semantic parity
Run old and new readers in parallel before the retirement date. Normalize identifiers and compare counts, principals, role definitions, scopes, assignment types, start times, and end times. Investigate differences instead of forcing equality: Iteration 3 models active instances, eligibility instances, and pending requests separately, so a mismatch may expose an old assumption rather than a new API defect.
5. Cut over with observable failure modes
Deploy behind a configuration switch where practical. Alert on non-2xx responses, empty result sets that violate a known baseline, pagination failures, permission errors, and latency increases. Record the new endpoint family and app registration in the runbook so responders do not restore the deprecated code during an incident.
Verification checklist before October 28
- No literal Iteration 2 URLs remain in owned repositories or configuration.
- Runtime telemetry shows no calls to the deprecated endpoint family during a complete business cycle.
- Every PIM integration has a named owner and an approved replacement route.
- New readers match expected active and eligible assignment populations.
- Create, activate, deactivate, renew, extend, and remove workflows are tested where used.
- Paging, throttling, national-cloud endpoints, and failure handling are covered.
- Workload identities have only the Graph permissions or Azure RBAC roles they need.
- Monitoring detects both API errors and suspiciously empty datasets.
- Rollback steps preserve the new API design; they do not depend on Iteration 2.
- Change records contain test output, approver, deployment time, and post-cutover evidence.
Related VigilSecureInfo guidance
Use the broader Azure security best-practices guide to review control-plane hardening around the migration. The identity and access management guide provides governance context for privileged access, while the GitHub App token migration playbook shows a similar inventory-first approach to a time-bound identity-platform change.
Conclusion
The October 28 retirement is close enough that discovery and parity testing should already be underway. The fastest safe path is to identify literal legacy calls, correlate them with runtime and ownership data, split migrations by provider, and prove the new schedule model returns the information each consumer actually needs. Treat a clean source scan as one evidence point, not the finish line, and keep the final cutover observable.
Primary sources
- Microsoft Entra release archive: Iteration 2 retirement announcement
- Microsoft Entra PIM API concepts and provider model
- Microsoft migration guidance and Azure operation mapping
- Microsoft Graph v1.0: list Entra role assignment schedule instances
- Azure Resource Manager: list role eligibility schedule requests







