Microsoft has introduced new Microsoft Graph APIs that give organizations a programmatic way to manage how Copilot credits are spent and tracked. These APIs are currently in public preview and should let teams responsible for Copilot in their organization view credit balances and set up and adjust spending policies in a more automated way.
This update has no impact on users, and there is no change to how they work with Copilot day to day. An organization can now connect Copilot cost management with existing financial and administrative processes.
If a user requests additional Copilot credits, their organization may be able to review and respond to that request through these new tools.
For financial reporting, the current preview offers only limited options: you can read the tenant credit balance and the balances of specific users, but there is no way to get a full consumption report for all users. You must already know the user (see the chapter “Reading credit balances and consumption”).
The APIs are only relevant to organizations that use Copilot credits with their Microsoft 365 Copilot setup. This first release focuses on spending policies and credit balance information and does not include detailed usage reporting.
If you are testing these APIs, you will notice many billing services are missing. The current release supports the following billing services. Check those services again later.
- Cowork
- Code
- Copilot in OneDrive
- Copilot in SharePoint

Those are all the Microsoft Copilot services.

Listing all spending services
To find supported services, I manually created a new spending policy in the Microsoft 365 admin center, enabled all billing services (then disabled the policy), and listed all policies via the Graph API.
Import-Module Microsoft.Graph.Authentication
Connect-MgGraph -Scopes "CopilotCostManagement-Policy.Read.All"
# Get all spending policies ordered by creation date descending
$Url = "https://graph.microsoft.com/beta/copilot/costManagement/spendingPolicies?`$orderby=createdDateTime desc"
$AllSpendingPolicies = Invoke-MgGraphRequest -Method GET -Uri $Url
$AllSpendingPolicies.value | select id,displayName,status,createdDateTimeCheck the spending policy ID.

List the spending services for that policy.
# Get the service settings of a specific spending policy
$Url = "https://graph.microsoft.com/beta/copilot/costManagement/spendingPolicies/57c717d5-10f2-47a3-9730-5c68b8bbc8aa/serviceSettings"
$ServiceSettings = Invoke-MgGraphRequest -Method GET -Uri $Url
$ServiceSettings.value | Select-Object serviceId, stateThe system returns all supported services and their internal service IDs. You need them to create a new policy.

The system returns all services, but the Graph API supports only the green-marked ones.

Creating a new spending policy
Before you create a new spending policy via Graph:
- The service must be supported via the Graph endpoint (cowork, workIQ, sharePointCopilot, or oneDriveCopilot). Pay close attention to the wording, as the Graph endpoint is case-sensitive.
- The Graph API has limitations; for example, you cannot yet add a specific Azure subscription as a billing method.
- You cannot update or delete a spending policy in the Microsoft 365 admin center if the policy was created via Graph APIs. The admin center will return an error. You must update or delete it via Graph again.
- Creating a policy also requires the Billing Administrator or Global Administrator role.
You need the permission CopilotCostManagement-Policy.ReadWrite.All to prepare a new policy, and CopilotCostManagement-Assignment.ReadWrite.All if you want to assign a group to that policy.
- Prepare a new policy for Cowork.
In my sample, I will restrict the policy to an Entra ID group (see the #microsoft.graph.selectedGroupsSpendingPolicy type). If you want to enable the policy for all users, use the #microsoft.graph.allUsersSpendingPolicy type instead.
Connect-MgGraph -Scopes "CopilotCostManagement-Policy.ReadWrite.All, CopilotCostManagement-Assignment.ReadWrite.All"
$Body = @{
"@odata.type" = "#microsoft.graph.selectedGroupsSpendingPolicy"
displayName = "Cowork by MSGraph"
periodLimit = @{
"@odata.type" = "#microsoft.graph.limitedCostManagementQuantity"
value = 1000
unit = "copilotCredit"
}
perUserLimit = @{
"@odata.type" = "#microsoft.graph.unlimitedCostManagementQuantity"
unit = "copilotCredit"
}
billingMethods = "payAsYouGo"
serviceSettings = @(
# Valid serviceIds (case-sensitive): cowork, workIQ, sharePointCopilot, oneDriveCopilot
@{ serviceId = "cowork"; state = "enabled" }
)
} | ConvertTo-Json -Depth 5
$Url = "https://graph.microsoft.com/beta/copilot/costManagement/spendingPolicies"
$NewPolicy = Invoke-MgGraphRequest -Method POST -Uri $Url -Body $Body -ContentType "application/json"- Add the group assignment to limit the usage to members of that group.
Note: Updates to a policy require current ETag information. The sample uses a small helper function to fetch the latest ETag.
$GroupId = "<GroupObjectID>"
$PolicyId = $NewPolicy.id
$PolicyUrl = "https://graph.microsoft.com/beta/copilot/costManagement/spendingPolicies/$PolicyId"
# Function to get the current ETag of the policy (it changes after every mutation)
function Get-PolicyEtag { (Invoke-MgGraphRequest -Method GET -Uri $PolicyUrl).'@odata.etag' }
# Assign the group
$Ref = @{ "@odata.id" = "https://graph.microsoft.com/beta/groups/$GroupId" } | ConvertTo-Json
Invoke-MgGraphRequest -Method POST -Uri "$PolicyUrl/microsoft.graph.selectedGroupsSpendingPolicy/assignedGroups/`$ref" -Body $Ref -ContentType "application/json" -Headers @{ "If-Match" = (Get-PolicyEtag) }The spending policy has been created; alerts are off by default.
Pay attention to the billing method: The Graph API offers no property to select the Azure billing account, so the policy's billing method configuration is not finished. You must update the method manually until Microsoft provides an API option.

Adding or removing cost alerts
Alerts are off by default. I first prepare the alert; in the second step, I enable it.
You can add multiple users, but you must run the cmdlets for each user (or a loop), as the endpoint does not support multiple users with one update. You must add the user object ID. A UserPrincipalName is not supported.
# 1. Create the alert configuration disabled, with the 80 % threshold
$Body = @{
isEnabled = $false
thresholds = @( @{ "@odata.type" = "#microsoft.graph.percentageCostManagementAlertThreshold"; value = 80 } )
} | ConvertTo-Json -Depth 5
Invoke-MgGraphRequest -Method PATCH -Uri "$PolicyUrl/emailThresholdSettings" -Body $Body -ContentType "application/json" -Headers @{ "If-Match" = (Get-PolicyEtag) }
# 2. Link the recipient
$UserId = "<UserObjectID>"
$Ref = @{ "@odata.id" = "https://graph.microsoft.com/beta/users/$UserId" } | ConvertTo-Json
Invoke-MgGraphRequest -Method POST -Uri "$PolicyUrl/emailThresholdSettings/recipients/`$ref" -Body $Ref -ContentType "application/json" -Headers @{ "If-Match" = (Get-PolicyEtag) }Enable (or disable) the alerts.
# 3. Enable (or disable) alerting
Invoke-MgGraphRequest -Method PATCH -Uri "$PolicyUrl/emailThresholdSettings" -Body (@{ isEnabled = $true } | ConvertTo-Json) -ContentType "application/json" -Headers @{ "If-Match" = (Get-PolicyEtag) }Configured as expected.

Deleting a spending policy
As mentioned before, you cannot update or delete a spending policy in the Microsoft 365 admin center if you created it via Microsoft Graph.

Just delete it via Microsoft Graph, and it’s gone.
Invoke-MgGraphRequest -Method DELETE -Uri $PolicyUrl -Headers @{ "If-Match" = (Get-PolicyEtag) }Reading credit balances and consumption
The reporting options are limited and not yet ready for a billing or financial team. There is no endpoint that lists the consumption of all users or of a spending policy. You can read the tenant credit balance and the balance of a specific user, but you must know the user object ID. Without it, you cannot get a full consumption list.
Read the tenant credit balance. You need the permission CopilotCostManagement.Read.All.
Connect-MgGraph -Scopes "CopilotCostManagement.Read.All, CopilotCostManagement-UserData.Read.All"
$Url = "https://graph.microsoft.com/beta/copilot/costManagement/getTenantCreditBalance"
$TenantCreditBalance = Invoke-MgGraphRequest -Method GET -Uri $Url
$TenantCreditBalance | ConvertTo-Json -Depth 5The result lists the consumed credits per billing method (prepaidCredits and payAsYouGo).

Read the balance of a specific user, including the balance per service. You need the permission CopilotCostManagement-UserData.Read.All. You must add the user object ID. A UserPrincipalName is not supported.
$UserId = "<UserObjectID>"
$Url = "https://graph.microsoft.com/beta/copilot/costManagement/userBalances/$UserId?`$expand=serviceBalances"
$UserBalance = Invoke-MgGraphRequest -Method GET -Uri $Url
$UserBalance | ConvertTo-Json -Depth 5The result contains one row per service, with the consumed credits (consumedQuantity), the configured limit, the remaining credits, and the spendingPolicyId that applies to this user.
The result was a surprise. Below is a copy of my result; pay attention to the spendingPolicyId.
Some values are redacted for privacy reasons.
{
"serviceBalances@odata.context": "https://graph.microsoft.com/beta/$metadata#copilot/costManagement/userBalances('00000000-0000-0000-0000-000000000000')/serviceBalances",
"id": "00000000-0000-0000-0000-000000000000",
"serviceBalances": [
{
"spendingPolicyId": "5a520702-2e18-4742-b2ae-3b0212c8b4e6",
"accessDecisionReason": "allowed",
"serviceId": "cowork",
"consumedQuantity": 0.0,
"effectiveCapacityQuantity": {
"value": 1000.0,
"unit": "copilotCredit",
"@odata.type": "#microsoft.graph.limitedCostManagementQuantity"
},
"isConsumptionAllowed": true,
"configuredPeriodLimit": {
"value": 1000.0,
"unit": "copilotCredit",
"@odata.type": "#microsoft.graph.limitedCostManagementQuantity"
},
"dataStatus": {
"lastModifiedDateTime": "2026-11-07T09:48:32.9577895Z"
},
"unit": "copilotCredit",
"configuredPerUserLimit": {
"@odata.type": "#microsoft.graph.unlimitedCostManagementQuantity",
"unit": "copilotCredit"
},
"remainingQuantity": {
"value": 1000.0,
"unit": "copilotCredit",
"@odata.type": "#microsoft.graph.limitedCostManagementQuantity"
}
}
],
"unevaluatedServices": [
{
"reason": "noApplicablePolicy",
"serviceId": "workIQ"
}
],
"@odata.context": "https://graph.microsoft.com/beta/$metadata#copilot/costManagement/userBalances(serviceBalances())/$entity",
"selectionDataStatus": {
"policyConfigurationAsOfDateTime": "2026-11-07T09:47:45.589555Z",
"evaluatedDateTime": "2026-11-07T09:48:32.9577895Z",
"groupMembershipAsOfDateTime": "2026-11-07T09:47:45.6463535Z",
"usageAsOfDateTime": "2026-11-07T09:48:32.9577895Z"
}
}The spendingPolicyId is the policy I previously created via Microsoft Graph. It seems the system ignores spending policies created in the Microsoft 365 admin center.

For my test, I used credits to see whether the API returns the spent credits. The consumption summary confirms the API didn’t return the latest information.

It’s an early preview. You may want to test it, but don’t trust it yet, it’s clearly not ready.




No comments yet