Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Create Agent Users in Microsoft Entra ID from Agent Identities, enabling AI agents to act as digital workers with user identity capabilities in Microsoft 365 and Azure environments.
.claude/skills/entra-agent-user/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 93% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 74% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 38% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 12% | 0% |
An agent user is a specialized user identity in Microsoft Entra ID that enables AI agents to act as digital workers. It allows agents to access APIs and services that strictly require user identities (e.g., Exchange mailboxes, Teams, org charts), while maintaining appropriate security boundaries.
Agent users receive tokens with idtyp=user, unlike regular agent identities which receive idtyp=app.
ServiceIdentity) created from an agent identity blueprintAgentIdUser.ReadWrite.IdentityParentedBy (least privileged)AgentIdUser.ReadWrite.AllUser.ReadWrite.All> Important: The identityParentId must reference a true agent identity (created via an agent identity blueprint), NOT a regular application service principal. You can verify by checking that the service principal has @odata.type: #microsoft.graph.agentIdentity and servicePrincipalType: ServiceIdentity.
Agent Identity Blueprint (application template)
│
├── Agent Identity (service principal - ServiceIdentity)
│ │
│ └── Agent User (user - agentUser) ← 1:1 relationship
│
└── Agent Identity Blueprint Principal (service principal in tenant)| Component | Type | Token Claim | Purpose | |---|---|---|---| | Agent Identity | Service Principal | idtyp=app | Backend/API operations | | Agent User | User (agentUser) | idtyp=user | Act as a digital worker in M365 |
Before creating an agent user, confirm the agent identity is a proper agentIdentity type:
httpGET https://graph.microsoft.com/beta/servicePrincipals/{agent-identity-id} Authorization: Bearer <token>
Verify the response contains:
json{ "@odata.type": "#microsoft.graph.agentIdentity", "servicePrincipalType": "ServiceIdentity", "agentIdentityBlueprintId": "<blueprint-id>" }
powershellConnect-MgGraph -Scopes "Application.Read.All" -TenantId "<tenant>" -UseDeviceCode -NoWelcome Invoke-MgGraphRequest -Method GET ` -Uri "https://graph.microsoft.com/beta/servicePrincipals/<agent-identity-id>" | ConvertTo-Json -Depth 3
> Common mistake: Using an app registration's appId or a regular application service principal's id will fail. Only agent identities created from blueprints work.
httpPOST https://graph.microsoft.com/beta/users/microsoft.graph.agentUser Content-Type: application/json Authorization: Bearer <token> { "accountEnabled": true, "displayName": "My Agent User", "mailNickname": "my-agent-user", "userPrincipalName": "my-agent-user@yourtenant.onmicrosoft.com", "identityParentId": "<agent-identity-object-id>" }
| Property | Type | Description | |---|---|---| | accountEnabled | Boolean | true to enable the account | | displayName | String | Human-friendly name | | mailNickname | String | Mail alias (no spaces/special chars) | | userPrincipalName | String | UPN — must be unique in the tenant (alias@verified-domain) | | identityParentId | String | Object ID of the parent agent identity |
powershellConnect-MgGraph -Scopes "User.ReadWrite.All" -TenantId "<tenant>" -UseDeviceCode -NoWelcome $body = @{ accountEnabled = $true displayName = "My Agent User" mailNickname = "my-agent-user" userPrincipalName = "my-agent-user@yourtenant.onmicrosoft.com" identityParentId = "<agent-identity-object-id>" } | ConvertTo-Json Invoke-MgGraphRequest -Method POST ` -Uri "https://graph.microsoft.com/beta/users/microsoft.graph.agentUser" ` -Body $body -ContentType "application/json" | ConvertTo-Json -Depth 3
400 Bad Request.userPrincipalName must be unique. Don't reuse an existing user's UPN.Assigning a manager allows the agent user to appear in org charts (e.g., Teams).
httpPUT https://graph.microsoft.com/beta/users/{agent-user-id}/manager/$ref Content-Type: application/json Authorization: Bearer <token> { "@odata.id": "https://graph.microsoft.com/beta/users/{manager-user-id}" }
powershell$managerBody = '{"@odata.id":"https://graph.microsoft.com/beta/users/<manager-user-id>"}' Invoke-MgGraphRequest -Method PUT ` -Uri "https://graph.microsoft.com/beta/users/<agent-user-id>/manager/`$ref" ` -Body $managerBody -ContentType "application/json"
A license is needed for the agent user to have a mailbox, Teams presence, etc. Usage location must be set first.
httpPATCH https://graph.microsoft.com/beta/users/{agent-user-id} Content-Type: application/json Authorization: Bearer <token> { "usageLocation": "US" }
httpGET https://graph.microsoft.com/beta/subscribedSkus?$select=skuPartNumber,skuId,consumedUnits,prepaidUnits Authorization: Bearer <token>
Requires Organization.Read.All permission.
httpPOST https://graph.microsoft.com/beta/users/{agent-user-id}/assignLicense Content-Type: application/json Authorization: Bearer <token> { "addLicenses": [ { "skuId": "<sku-id>" } ], "removeLicenses": [] }
powershellConnect-MgGraph -Scopes "User.ReadWrite.All","Organization.Read.All" -TenantId "<tenant>" -NoWelcome # Set usage location Invoke-MgGraphRequest -Method PATCH ` -Uri "https://graph.microsoft.com/beta/users/<agent-user-id>" ` -Body '{"usageLocation":"US"}' -ContentType "application/json" # Assign license $licenseBody = '{"addLicenses":[{"skuId":"<sku-id>"}],"removeLicenses":[]}' Invoke-MgGraphRequest -Method POST ` -Uri "https://graph.microsoft.com/beta/users/<agent-user-id>/assignLicense" ` -Body $licenseBody -ContentType "application/json"
> Tip: You can also assign licenses via the Entra admin center under Identity → Users → All users → select the agent user → Licenses and apps.
| Service | Estimated Time | |---|---| | Exchange mailbox | 5–30 minutes | | Teams availability | 15 min – 24 hours | | Org chart / People search | Up to 24–48 hours | | SharePoint / OneDrive | 5–30 minutes | | Global Address List | Up to 24 hours |
idtyp=user tokens)| Error | Cause | Fix | |---|---|---| | Agent user IdentityParent does not exist | identityParentId points to a non-existent or non-agent-identity object | Verify the ID is an agentIdentity service principal, not a regular app | | 400 Bad Request (identityParentId already linked) | The agent identity already has an agent user | Each agent identity supports only one agent user | | 409 Conflict on UPN | The userPrincipalName is already taken | Use a unique UPN | | License assignment fails | Usage location not set | Set usageLocation before assigning licenses |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 17,572 | 10,244 | -42% | 1 | 1 | 0% | 3,580 | 4,875 | +36% | 0 | 0 | — |
case-02 | fail→pass | 14,701 | 13,295 | -10% | 1 | 1 | 0% | 2,936 | 5,668 | +93% | 0 | 0 | — |
case-03 | fail→pass | 11,274 | 6,669 | -41% | 1 | 1 | 0% | 2,274 | 3,954 | +74% | 0 | 0 | — |
case-04 | fail→pass | 13,141 | 5,004 | -62% | 1 | 1 | 0% | 2,449 | 3,376 | +38% | 0 | 0 | — |
case-05 | fail→pass | 14,432 | 3,374 | -77% | 1 | 1 | 0% | 2,792 | 3,136 | +12% | 0 | 0 | — |
case-06 | fail→fail | 10,921 | 5,814 | -47% | 1 | 1 | 0% | 2,035 | 3,393 | +67% | 0 | 0 | — |
case-07 | fail→pass | 13,460 | 4,673 | -65% | 1 | 1 | 0% | 1,894 | 3,307 | +75% | 0 | 0 | — |
case-08 | fail→fail | 7,365 | 2,857 | -61% | 1 | 1 | 0% | 1,303 | 3,031 | +133% | 0 | 0 | — |
case-09 | fail→fail | 3,624 | 3,563 | -2% | 1 | 1 | 0% | 630 | 3,058 | +385% | 0 | 0 | — |
case-10 | fail→pass | 5,912 | 3,014 | -49% | 1 | 1 | 0% | 1,279 | 3,133 | +145% | 0 | 0 | — |
case-11 | fail→pass | 8,360 | 4,121 | -51% | 1 | 1 | 0% | 1,681 | 3,246 | +93% | 0 | 0 | — |
case-12 | fail→pass | 12,898 | 5,333 | -59% | 1 | 1 | 0% | 2,228 | 3,175 | +43% | 0 | 0 | — |
case-13 | fail→pass | 10,873 | 6,327 | -42% | 1 | 1 | 0% | 1,982 | 3,565 | +80% | 0 | 0 | — |
case-14 | fail→pass | 8,828 | 7,030 | -20% | 1 | 1 | 0% | 1,605 | 3,103 | +93% | 0 | 0 | — |
case-15 | fail→pass | 8,006 | 3,692 | -54% | 1 | 1 | 0% | 1,306 | 2,795 | +114% | 0 | 0 | — |
case-16 | pass→pass | 9,769 | 4,886 | -50% | 1 | 1 | 0% | 1,549 | 3,384 | +118% | 0 | 0 | — |
case-17 | fail→pass | 9,304 | 2,884 | -69% | 1 | 1 | 0% | 1,627 | 2,968 | +82% | 0 | 0 | — |
case-18 | fail→pass | 10,505 | 5,101 | -51% | 1 | 1 | 0% | 2,053 | 3,321 | +62% | 0 | 0 | — |
case-19 | fail→fail | 10,046 | 3,908 | -61% | 1 | 1 | 0% | 1,860 | 3,158 | +70% | 0 | 0 | — |
case-20 | fail→fail | 7,035 | 5,853 | -17% | 1 | 1 | 0% | 1,394 | 3,632 | +161% | 0 | 0 | — |
case-21 | fail→fail | 6,404 | 6,763 | +6% | 1 | 1 | 0% | 1,271 | 3,828 | +201% | 0 | 0 | — |
case-22 | fail→fail | 5,106 | 3,425 | -33% | 1 | 1 | 0% | 907 | 3,104 | +242% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +64 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.