Fix Azure Governance Policy Assignment Errors

Microsoft Fix Intermediate 14 min read Official Docs Grounded Updated April 20, 2026

Why This Is Happening

You've spent the better part of an afternoon trying to get your Azure governance policy assignment to stick. Maybe the portal keeps throwing a vague AuthorizationFailed error. Maybe you built the REST API call perfectly , or so you thought , and you're getting a 400 Bad Request with a cryptic message about an invalid policyDefinitionId. Or maybe the policy assigned just fine, but your resources stubbornly show as Non-compliant when they definitely shouldn't be. I've seen all of these. I've lived all of these.

Azure governance policy is one of the most powerful tools in any cloud administrator's toolkit. Done right, it enforces standards across every subscription, resource group, and individual resource in your organization, automatically, at scale. Done wrong, it can block legitimate deployments, confuse your team, and generate a flood of non-compliance alerts that nobody trusts anymore.

Here's the real problem: Azure policy assignment errors almost never tell you exactly what's broken. The portal error messages are written for engineers who already understand the system. If you're newer to Azure governance policy, or you're configuring policy assignments via the REST API for the first time, that gap between "error occurred" and "here's why" can feel enormous.

The most common root causes I see are:

  • Scope misconfiguration, The scope pattern in your REST API call doesn't match any valid Azure resource hierarchy level. Management group, subscription, resource group, and individual resource scopes each have their own exact URI format, and a single misplaced slash breaks the whole thing.
  • Wrong or missing policyDefinitionId, If you're referencing a built-in policy, the GUID must be exact. If you're referencing a custom definition, you need the full resource path, not just the display name.
  • Insufficient permissions on the assignment scope, You need Microsoft.Authorization/policyAssignments/write at the target scope. A lot of teams discover they have Contributor but not the right Authorization permissions when they hit this wall.
  • API version mismatch, Using a deprecated api-version in your REST calls returns silent failures or schema validation errors that make no sense until you check the version string.
  • Malformed JSON request body, The nonComplianceMessages array in particular trips people up. One missing bracket and the whole payload gets rejected.

The frustrating part is that none of these are exotic edge cases. They're the first five things that go wrong for almost everyone. I know this is frustrating, especially when your governance rollout is blocking a production deployment. Let's fix it. Browse all Microsoft fix guides →

The Quick Fix, Try This First

Before you go deep into diagnostics, run this sanity check. The majority of Azure policy assignment failures I've seen in enterprise environments come down to one thing: the scope URI is wrong. Fix the scope, and everything else usually falls into place.

Open a Visual Studio Code terminal session and connect to Azure using the Azure CLI:

az login

If you're working across multiple subscriptions, which is extremely common in governance scenarios, list them and explicitly set the right one:

az account list --output table
az account set --subscription <subscriptionID>

Now verify that your scope string matches one of the four valid patterns exactly. For a resource group assignment, it must look like this:

/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}

For a subscription-level Azure governance policy assignment:

/subscriptions/{subscriptionId}

For a management group (the broadest scope, covering multiple subscriptions at once):

/providers/Microsoft.Management/managementGroups/{managementGroup}

And for a single resource:

/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}

Take your scope string, compare it character-by-character against these patterns. Check for trailing slashes (not allowed), double slashes (not allowed), and any spaces that crept in (definitely not allowed). Nine times out of ten, fixing the scope resolves the error immediately.

Also confirm you're using api-version=2023-04-01 in your REST call. Older versions like 2021-06-01 are still technically accepted in some scenarios but can silently drop newer fields like nonComplianceMessages in a way that's hard to diagnose.

Pro Tip
Always run az account show before any policy assignment operation to confirm the active subscription context. I've wasted entire debugging sessions because my CLI was pointed at a sandbox subscription while my policy definition lived in production. The error messages give you no hint this is happening.
1
Authenticate and Set Subscription Context

Every Azure governance policy operation starts here. Even if you think you're already logged in, run az login fresh. Token expiry is silent and causes confusing permission errors that look like scope or definition problems.

az login

Your default browser opens for interactive authentication. If you're in a headless environment or a CI pipeline, use az login --use-device-code instead. Once authenticated, list all available subscriptions to confirm you're looking at the right one:

az account list --output table

The output shows you Name, CloudName, SubscriptionId, State, and IsDefault. Find the subscription where your policy assignment needs to live and set it as your active context:

az account set --subscription <subscriptionID>

Replace <subscriptionID> with the actual GUID from the table output, not the display name, which can contain spaces and special characters that cause problems downstream. Confirm the switch worked:

az account show --output table

You should see your target subscription listed as the active one. If you're assigning an Azure governance policy at the management group scope, you'll also want to confirm your account has the Management Group Contributor or Owner role at that management group level, the subscription context alone isn't enough for management group operations.

If this step succeeds cleanly, you'll see your subscription details returned without any error. That's your green light to move to building the request body.

2
Build and Validate Your JSON Request Body

This is where most people's Azure policy assignment REST API calls fail. The JSON body needs to be exactly right, structurally and semantically. Open your editor and create a file named request-body.json. The required structure looks like this:

{
  "properties": {
    "displayName": "Audit VM managed disks",
    "description": "Policy assignment to resource group scope created with REST API",
    "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d",
    "nonComplianceMessages": [
      {
        "message": "Virtual machines should use managed disks"
      }
    ]
  }
}

Let me break down what each field does and where it commonly goes wrong:

displayName, This is what shows in the portal's policy list. Keep it human-readable and descriptive. Maximum 128 characters. This is separate from the policyAssignmentName you'll use in the URL, that one needs to be alphanumeric with hyphens only, no spaces.

description, Adds context about why this policy assignment exists. Invaluable six months from now when nobody remembers. Don't skip it.

policyDefinitionId, This is the most error-prone field. For built-in Azure policy definitions, the path starts with /providers/Microsoft.Authorization/policyDefinitions/ followed by the definition's GUID. For custom definitions scoped to a subscription, it starts with /subscriptions/{subscriptionId}/providers/Microsoft.Authorization/policyDefinitions/. Get this wrong and you'll get a PolicyDefinitionNotFound error.

nonComplianceMessages, This is an array of objects. Each object has a message string. The most common mistake is omitting the outer array brackets or the inner object braces, turning it into a bare string. That breaks JSON schema validation immediately.

Save the file, then validate the JSON is well-formed before making the API call: cat request-body.json | python3 -m json.tool. If Python is unavailable, paste the content into any online JSON validator. Never skip this check.

3
Construct the Correct REST API URI

The Azure governance policy assignment REST API uses a PUT request to this endpoint:

https://management.azure.com/{scope}/providers/Microsoft.Authorization/policyAssignments/{policyAssignmentName}?api-version=2023-04-01

You need to substitute two values: {scope} and {policyAssignmentName}.

For a resource group scoped assignment, the most common starting point, your full URI looks like:

https://management.azure.com/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/MyResourceGroup/providers/Microsoft.Authorization/policyAssignments/audit-vm-managed-disks?api-version=2023-04-01

A few things to watch carefully here:

The policyAssignmentName segment at the end, audit-vm-managed-disks in my example, becomes part of the assignment's policyAssignmentId property. This name must be unique within the scope. If a policy assignment with that name already exists at that scope, the PUT call will update it (which may or may not be what you want). Choose names that are descriptive and won't collide with existing assignments.

The scope segment sits between the base management URL and the /providers/Microsoft.Authorization/policyAssignments/ path. It does not have a trailing slash. This catches people constantly, the URI construction feels unnatural because you're inserting a full resource path mid-URL.

For a management group scope, the URI becomes:

https://management.azure.com/providers/Microsoft.Management/managementGroups/MyMgmtGroup/providers/Microsoft.Authorization/policyAssignments/my-assignment?api-version=2023-04-01

Note that management group scope starts with /providers/Microsoft.Management/, not /subscriptions/. If you're used to subscription-scoped calls, this trips you up the first time.

When the URI is correct and the PUT succeeds, the API returns a 201 Created response with the full assignment object in the body, including the generated policyAssignmentId. Save that ID, you'll need it to track compliance and manage the assignment later.

4
Execute the Assignment with az rest

With your request-body.json ready and your URI confirmed, execute the policy assignment using the az rest command. This approach is recommended over raw curl calls because az rest automatically handles authentication token injection using your active az login session.

az rest \
  --method PUT \
  --uri "https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Authorization/policyAssignments/audit-vm-managed-disks?api-version=2023-04-01" \
  --body @request-body.json

Replace {subscriptionId} and {resourceGroupName} with your actual values before running. The @request-body.json syntax tells the CLI to read the body from your file, make sure the terminal's working directory is where you saved that file, or provide the full path.

On success, you'll see JSON output starting with something like:

{
  "id": "/subscriptions/.../resourceGroups/.../providers/Microsoft.Authorization/policyAssignments/audit-vm-managed-disks",
  "name": "audit-vm-managed-disks",
  "type": "Microsoft.Authorization/policyAssignments",
  "properties": {
    "displayName": "Audit VM managed disks",
    "policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/06a78e20-9358-41c9-923c-fb736d382a4d",
    ...
  }
}

That id field at the top is your policyAssignmentId. Copy it somewhere safe.

If you get a 400 Bad Request, the error body will usually contain a code and message field. Common codes at this stage include InvalidRequestContent (malformed JSON), PolicyDefinitionNotFound (bad definition ID), and InvalidScope (scope URI format error). Each of those points back to a specific field to fix.

If you get 403 Forbidden, your account lacks Microsoft.Authorization/policyAssignments/write permission at the target scope. That's a role assignment issue, not a policy issue, you'll need an Owner or User Access Administrator to grant that permission before you retry.

5
Verify Compliance Evaluation and Non-Compliance Messages

Your Azure governance policy assignment is created, but the work isn't done yet. Azure policy compliance evaluation doesn't happen instantly, there's a background evaluation cycle that typically runs every 24 hours, though you can trigger an on-demand scan for faster feedback.

Trigger an on-demand compliance scan for your subscription using the Azure CLI:

az policy state trigger-scan --subscription {subscriptionId}

Or scope it to just a resource group to be faster:

az policy state trigger-scan \
  --resource-group {resourceGroupName}

After the scan completes (this can take 5–30 minutes depending on the number of resources), check the compliance state:

az policy state list \
  --resource-group {resourceGroupName} \
  --policy-assignment audit-vm-managed-disks \
  --output table

Look at the complianceState column. Resources showing NonCompliant are ones the policy definition flagged. For the "Audit VMs that do not use managed disks" policy, those will be any VMs still using unmanaged disk storage.

Here's the thing about non-compliance messages that most guides don't explain clearly: the message you set in nonComplianceMessages during assignment creation is what end users see in the portal when they try to take a blocked action (for Deny effect policies) or when reviewing compliance findings (for Audit effect policies). It's worth making that message actionable, not just "this is non-compliant" but "contact your cloud ops team to migrate to managed disks before the Q3 deadline."

If your non-compliance message isn't showing up correctly, verify the field was saved properly by retrieving the assignment:

az policy assignment show \
  --name audit-vm-managed-disks \
  --resource-group {resourceGroupName}

The nonComplianceMessages array should appear in the output. If it's missing or empty, the JSON in your request body had a structural error and the field was silently ignored, go back to Step 2 and recheck the array syntax, then update the assignment with a corrected PUT call.

Advanced Troubleshooting

If the five steps above haven't resolved your Azure governance policy assignment issue, you're dealing with something more specific. Here's where I dig into the scenarios I see most in enterprise environments.

Management Group Scope, Permissions Inheritance Pitfalls

When you assign policy at the management group scope, it cascades down to all subscriptions and resource groups within that management group. That's exactly what you want for organization-wide governance. But it also means that if someone has already assigned a conflicting policy at a lower scope, you can end up with ambiguous compliance results.

Check for conflicting assignments at multiple scope levels:

az policy assignment list \
  --scope /providers/Microsoft.Management/managementGroups/{managementGroup} \
  --output table

Then check at the subscription level to see if there's an override or duplicate:

az policy assignment list \
  --scope /subscriptions/{subscriptionId} \
  --output table

Policy Definition ID, Custom vs. Built-in

The policyDefinitionId field in your request body is where scope confusion creates the hardest-to-diagnose errors. Built-in definitions use the path /providers/Microsoft.Authorization/policyDefinitions/{definitionGUID} with no subscription prefix. Custom definitions scoped to a specific subscription include that subscription in the path: /subscriptions/{subscriptionId}/providers/Microsoft.Authorization/policyDefinitions/{definitionName}. Custom definitions scoped to a management group use the management group path instead.

Using a subscription-scoped custom definition ID when assigning at a management group scope gives you a PolicyDefinitionNotFound error that looks exactly like a typo in the GUID. It's not, it's a scope mismatch between definition and assignment. The definition must exist at the same scope level or a parent scope of where you're assigning it.

Event Log Analysis for Silent Failures

Azure Activity Log captures every policy assignment operation. When a policy assignment fails silently or produces unexpected compliance results, this is your first stop:

az monitor activity-log list \
  --resource-provider Microsoft.Authorization \
  --query "[?operationName.value=='Microsoft.Authorization/policyAssignments/write']" \
  --output table

Filter by time range if the log is noisy:

az monitor activity-log list \
  --start-time 2026-04-20T00:00:00Z \
  --resource-provider Microsoft.Authorization \
  --output jsonc

Look at the status.value field. A Failed status with Succeeded sub-operations is a classic sign of a partial write, some properties saved, others didn't. That's often the nonComplianceMessages field getting dropped due to a schema issue.

Role Assignment Requirements

For policy assignments with a DeployIfNotExists or Modify effect, not just Audit, Azure creates a managed identity for the assignment and needs to grant that identity appropriate roles to perform remediation. If the managed identity wasn't granted the right roles at creation time, remediation tasks will fail even though the assignment itself looks healthy. Check this in the portal under Policy > Assignments > [Your Assignment] > Managed Identity, or review it with:

az policy assignment show \
  --name {policyAssignmentName} \
  --scope {scope} \
  --query "identity"
When to Call Microsoft Support
If you've confirmed your scope URI is correct, your JSON is valid, your permissions include Microsoft.Authorization/policyAssignments/write, you're using api-version=2023-04-01, and you're still getting 500 Internal Server Error responses from the management API, that's a platform-side issue, not a configuration issue. Similarly, if your compliance evaluation scan never completes after 48 hours despite a successful trigger, something is broken on the backend. These are the two scenarios where escalating to Microsoft Support is the right call. File a severity B ticket with your Activity Log entries and the full error JSON response body attached.

Prevention & Best Practices

Getting Azure governance policy assignments right once is good. Not having to debug them again is better. Here's how teams that manage governance at scale keep these issues from recurring.

Version-pin your API calls. The current stable API version is 2023-04-01. When you're writing scripts, automation pipelines, or Terraform configurations that talk to the policy assignment REST API, hardcode that version string. Don't use "latest" shortcuts or rely on SDK defaults without checking what version they resolve to. When Microsoft releases a new API version, test before migrating, new versions occasionally change field behavior in ways that break existing assignment logic.

Define your naming convention before you scale. The policyAssignmentName in the URI and the displayName in the body serve different purposes. The assignment name is a key in the resource path, it can't have spaces, should be lowercase with hyphens, and needs to be unique per scope. The display name is for humans. Decide on a naming pattern early: something like {policy-type}-{scope-short}-{date} prevents collision headaches as your assignment count grows past dozens into the hundreds.

Store your request-body.json files in version control. Every policy assignment your organization uses should have its JSON definition in a Git repository, alongside the az rest command used to deploy it. This gives you a full audit trail, enables peer review of governance changes, and makes rollback as simple as running the previous version's command.

Test in a non-production scope first. Before assigning a policy with a Deny effect at the subscription or management group level, always test it at a single resource group with an Audit effect first. Check the compliance results for a full evaluation cycle. Understand what would have been blocked. Then switch to Deny. I've seen Deny-effect policies roll out to production and immediately block critical infrastructure deployments because nobody checked what was already in the environment.

Quick Wins
  • Run az account show before every policy operation to confirm active subscription context
  • Validate JSON with python3 -m json.tool before every az rest call
  • Use az policy state trigger-scan after assignment creation instead of waiting 24 hours for compliance data
  • Store policy assignment names and their policyAssignmentId values in a shared team doc, you'll need them for updates and deletions

Frequently Asked Questions

Why does my Azure policy assignment keep returning 403 even though I'm the subscription Owner?

Being subscription Owner usually means you have Microsoft.Authorization/policyAssignments/write, but not always. If your subscription is part of a management group with an Azure Policy that denies the write action to certain principals, the management group policy overrides your subscription role. Check the policy assignments at the management group level above your subscription using az policy assignment list --scope /providers/Microsoft.Management/managementGroups/{mgmtGroup}. Look for any Deny-effect assignments targeting authorization operations. If you find one, you'll need a management group Owner to modify or exempt your account.

How long does it take for an Azure governance policy assignment to show compliance results?

By default, the background evaluation cycle runs every 24 hours, so you can wait up to a full day before new resources appear in compliance reports. That's too slow for most workflows. Run az policy state trigger-scan --resource-group {yourResourceGroup} to kick off an on-demand scan, it typically completes in 5 to 30 minutes depending on how many resources exist in scope. For subscriptions with thousands of resources, budget closer to an hour. After the scan finishes, query results with az policy state list.

What's the difference between policyAssignmentName in the URL and displayName in the JSON body?

policyAssignmentName is the resource identifier, it forms part of the assignment's full resource ID (policyAssignmentId) and must follow ARM naming rules: alphanumeric characters and hyphens only, no spaces, maximum 64 characters. It's what you use in future GET, PUT, or DELETE operations targeting that specific assignment. displayName in the JSON body is purely for human readability, it's what shows up in the Azure portal's policy list, compliance dashboards, and governance reports. You can change displayName via a PUT update without affecting the assignment's identity; you cannot change policyAssignmentName without deleting and recreating the assignment.

Can I assign the same policy definition multiple times at the same scope?

Yes, you can assign the same policy definition multiple times at the same scope, as long as each assignment has a unique policyAssignmentName. This is actually a common pattern when you want the same policy to apply with different parameters to different resource groups within a subscription, or when you want separate non-compliance messages for different teams. Each assignment is evaluated independently, so resources can show as non-compliant under multiple assignments simultaneously. Just be careful with Deny-effect policies, having two Deny assignments for the same behavior doesn't add protection, it just adds noise to your compliance reports.

My nonComplianceMessages field isn't showing up after assignment, what went wrong?

This almost always means the nonComplianceMessages field in your JSON request body had a structural error that caused it to be silently dropped rather than causing the entire request to fail. The most common mistake is formatting it as a plain string or a flat object instead of an array of message objects. It must be structured as "nonComplianceMessages": [{"message": "Your message text here"}], an array containing objects, each with a message key. Validate your JSON with python3 -m json.tool, fix the structure, then update the assignment by rerunning the PUT call with the corrected body. PUT is idempotent, so this is safe to rerun.

How do I delete an Azure policy assignment I created via REST API?

Use the same URI pattern as your PUT call but switch to a DELETE method. With the Azure CLI it looks like: az rest --method DELETE --uri "https://management.azure.com/{scope}/providers/Microsoft.Authorization/policyAssignments/{policyAssignmentName}?api-version=2023-04-01". You need the same Microsoft.Authorization/policyAssignments/write permission at the target scope to delete as you needed to create. After deletion, existing non-compliance states for resources are not immediately cleared, they'll disappear after the next evaluation cycle or after you trigger an on-demand scan. Note that deleting an assignment does not delete the underlying policy definition; those are separate resources.

Related Microsoft Fix Guides

H
Sai Kiran Pandrala
Our team includes certified Microsoft engineers, Azure architects, and system administrators with 10+ years of enterprise IT experience. Every guide is written from hands-on troubleshooting, not guesswork. We test every fix before publishing.