Let’s assume you have a Microsoft Foundry resource with a project inside it, and an app that calls one of the APIs on that resource using a managed identity. You did the right thing: no keys, Entra authentication only, and a least-privilege role assigned to the identity. You open the portal, you can see the role assignment sitting there on the resource, and yet every call comes back with this:

1{
2  "error": {
3    "code": "PermissionDenied",
4    "message": "The principal `<object-id>` lacks the required data action
5    `Microsoft.CognitiveServices/accounts/ContentSafety/text:shieldprompt/action`
6    to perform `POST /contentsafety/text:shieldPrompt` operation."
7  }
8}

The role is there. The error says it isn’t. Both are true, and the reason is the Foundry resource hierarchy.

Two scopes that look like one

A Foundry resource is an account, and projects live inside it:

/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<resource>
/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<resource>/projects/<project>

Those are two different scopes. Azure RBAC inherits downward, never upward, so a role assigned on the project does not apply to the account above it. If your app calls an account-level endpoint, a project-scoped role assignment is invisible to it.

The portal does not make this obvious. When you assign a role from inside a project, you get a confirmation and the assignment shows up where you expect. Nothing tells you that the call you are trying to authorise lands one level higher.

Spotting it

The fastest way to see what is actually assigned is the CLI, because it shows you the scope string:

1az role assignment list --assignee <object-id> --all --query "[].{role:roleDefinitionName, scope:scope}" -o table

In my case this returned something like the following, and the problem is visible once you read the end of each scope:

Cognitive Services OpenAI User   .../accounts/my-foundry-resource
Cognitive Services User          .../accounts/my-foundry-resource/projects/my-project

The first role sits on the account. The second one sits on the project. The API call that kept failing was going to the account, so only the first assignment counted.

Warning
If this command returns nothing at all, check which subscription your CLI session is using before you conclude anything. I spent a while convinced the identity had no role assignments whatsoever, when in fact I was querying the wrong subscription. An empty result is not the same as no permissions.

Fixing it

Assign the role again, this time at the account scope:

1az role assignment create --assignee-object-id <object-id> --assignee-principal-type ServicePrincipal --role "Cognitive Services User" --scope "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<resource>"

Then verify it landed where you wanted:

1az role assignment list --assignee <object-id> --all --query "[].{role:roleDefinitionName, scope:scope}" -o table

You can leave the project-scoped assignment in place if something else needs it. It is not doing any harm, it just was not doing the job you thought it was.

Two things that will cost you time afterwards

Propagation. Role assignments are not instant. Give it around five minutes before you decide the fix did not work. A failure in the first minute tells you nothing.

Cached tokens. This one caught me out properly. If your app caches its access token, and most SDK credential implementations do, it will keep presenting the token it acquired before the role existed. That token carries no new permission, so the call keeps failing and the portal keeps showing a perfectly good role assignment. Restart the app after the assignment propagates and the next token picks up the change.

Why this is worth knowing

The account and project split is new enough that most guidance still talks about assigning roles “on the resource” without saying which one. If you are following along with a tutorial and it fails, this is one of the first things worth checking.

It also has a wider point attached to it. In Foundry, the project boundary is not only an organisational convenience, it is an RBAC scope. Where you draw it decides what your identities can reach. That is worth thinking about before you provision, not after.

That’s it. If a call to a Foundry resource returns PermissionDenied and the role is visibly assigned, read the scope string to the end before you start granting broader permissions. The answer is usually one path segment away.

Reference: https://learn.microsoft.com/en-us/azure/role-based-access-control/scope-overview