如何关联 Microsoft Graph API 与账单 API
结论
Microsoft Graph API 和 Microsoft Cost Management API 可以在同一个应用中配合使用,但没有一个“关联接口”能让二者直接互相调用。
首先要区分费用类型:
- 查询 Azure 订阅中的资源消耗、Marketplace 软件或按量计费许可证时,可以通过 Azure Resource Manager 获取
subscriptionId,再调用 Microsoft Cost Management API。 - 查询 Microsoft 365、Office 365、Dynamics 365 等按用户分配的许可证时,Microsoft Graph 的
/subscribedSkus只能返回许可证数量和服务计划,不包含价格。Azure Cost Management API 通常也不会提供这些许可证的账单明细。 - 企业通过 CSP 合作伙伴购买许可证时,价格、订单和发票通常需要通过 Partner Center API 查询。
- 如果只需要 Azure 产品的公开目录价,可以查询 Azure Retail Prices API。不过,公开目录价不等于企业协议价、CSP 价或实际发票金额。
因此,无法简单地从 Graph API 取得许可证对应的 Azure subscriptionId,再用它查询所有许可证费用。Azure 订阅和 Microsoft 365 租户许可证是两类不同的计费对象。
为什么 Graph API 无法直接返回许可证费用
Microsoft Graph 可以查询租户已经订阅的 SKU:
GET https://graph.microsoft.com/v1.0/subscribedSkus
典型响应包含:
{
"skuId": "00000000-0000-0000-0000-000000000000",
"skuPartNumber": "ENTERPRISEPACK",
"consumedUnits": 85,
"prepaidUnits": {
"enabled": 100,
"suspended": 0,
"warning": 0
},
"servicePlans": []
}
这些信息可以用来确认:
- 租户拥有哪些产品 SKU;
- 购买或启用了多少席位;
- 已分配多少席位;
- SKU 包含哪些服务计划。
响应中不包含单价、折扣、税费、账期或发票金额。许可证的实际价格可能受到购买渠道、合同期限、币种、企业协议和合作伙伴折扣等因素影响,不能通过 skuId 或 skuPartNumber 准确推算。
Microsoft Graph 主要处理 Microsoft 365、Entra ID 等租户数据。Azure subscriptionId 属于 Azure Resource Manager 的资源范围,通常不应通过 Graph API 获取。
查询 Azure 订阅费用的正确流程
1. 获取 Azure 订阅列表
调用 Azure Resource Manager Subscriptions API:
GET https://management.azure.com/subscriptions?api-version=2022-12-01
Authorization: Bearer <ARM_ACCESS_TOKEN>
响应中的 subscriptionId 可以作为 Cost Management API 的查询范围:
{
"value": [
{
"subscriptionId": "11111111-2222-3333-4444-555555555555",
"displayName": "Production",
"state": "Enabled"
}
]
}
调用方还需要对相应订阅拥有足够的 Azure RBAC 权限。应用仅在 Entra ID 中完成注册,并不会自动获得费用读取权限。
2. 查询订阅成本
取得 subscriptionId 后,可以调用 Cost Management Query API:
POST https://management.azure.com/subscriptions/11111111-2222-3333-4444-555555555555/providers/Microsoft.CostManagement/query?api-version=2023-11-01
Authorization: Bearer <ARM_ACCESS_TOKEN>
Content-Type: application/json
请求示例:
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "2026-09-01T00:00:00Z",
"to": "2026-09-13T23:59:59Z"
},
"dataset": {
"granularity": "None",
"aggregation": {
"totalCost": {
"name": "Cost",
"function": "Sum"
}
},
"grouping": [
{
"type": "Dimension",
"name": "ServiceName"
}
]
}
}
返回结果通常由列定义和数据行组成:
{
"properties": {
"columns": [
{
"name": "Cost",
"type": "Number"
},
{
"name": "ServiceName",
"type": "String"
},
{
"name": "Currency",
"type": "String"
}
],
"rows": [
[
125.48,
"Virtual Machines",
"USD"
]
]
}
}
查询 Marketplace 产品或软件许可费用时,还可以按照 API 实际支持的维度进一步分组或过滤,例如 PublisherType、ServiceName、MeterCategory 或 Product。可用维度会因计费账户类型和数据范围而有所不同,不能假定每个租户都会返回相同字段。
在一个程序中同时调用 Graph 和 Cost Management
两个 API 都可以使用 Microsoft Entra ID 进行身份认证,但访问令牌的受众不同。Graph API 的令牌不能直接用于 Azure Resource Manager。
下面用客户端凭据模式演示基本结构:
import { ConfidentialClientApplication } from "@azure/msal-node";
const tenantId = process.env.AZURE_TENANT_ID;
const clientId = process.env.AZURE_CLIENT_ID;
const clientSecret = process.env.AZURE_CLIENT_SECRET;
const app = new ConfidentialClientApplication({
auth: {
clientId,
authority: `https://login.microsoftonline.com/${tenantId}`,
clientSecret
}
});
async function getAccessToken(scope) {
const result = await app.acquireTokenByClientCredential({
scopes: [scope]
});
if (!result?.accessToken) {
throw new Error(`Unable to obtain token for ${scope}`);
}
return result.accessToken;
}
async function getSubscribedSkus() {
const token = await getAccessToken(
"https://graph.microsoft.com/.default"
);
const response = await fetch(
"https://graph.microsoft.com/v1.0/subscribedSkus",
{
headers: {
Authorization: `Bearer ${token}`
}
}
);
if (!response.ok) {
throw new Error(`Graph API failed: ${response.status} ${await response.text()}`);
}
return response.json();
}
async function getAzureSubscriptions() {
const token = await getAccessToken(
"https://management.azure.com/.default"
);
const response = await fetch(
"https://management.azure.com/subscriptions?api-version=2022-12-01",
{
headers: {
Authorization: `Bearer ${token}`
}
}
);
if (!response.ok) {
throw new Error(`Subscriptions API failed: ${response.status} ${await response.text()}`);
}
return response.json();
}
async function querySubscriptionCost(subscriptionId) {
const token = await getAccessToken(
"https://management.azure.com/.default"
);
const endpoint =
`https://management.azure.com/subscriptions/${encodeURIComponent(subscriptionId)}` +
"/providers/Microsoft.CostManagement/query?api-version=2023-11-01";
const response = await fetch(endpoint, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
type: "ActualCost",
timeframe: "MonthToDate",
dataset: {
granularity: "None",
aggregation: {
totalCost: {
name: "Cost",
function: "Sum"
}
},
grouping: [
{
type: "Dimension",
name: "ServiceName"
}
]
}
})
});
if (!response.ok) {
throw new Error(`Cost Management API failed: ${response.status} ${await response.text()}`);
}
return response.json();
}
这里的“关联”需要由应用自己的业务逻辑完成:
同一个 Entra ID 应用
├── Graph access token → 查询 subscribedSkus
└── ARM access token → 查询 Azure subscriptions 和 costs
应用可以把两组结果并排展示在数据库或报表中,但不能只凭 SKU 名称或租户 ID,就认定某项 Microsoft 365 许可证成本对应某个 Azure 订阅。
查询 Microsoft 365 许可证费用时的选择
如果这里的“许可证”是指 Microsoft 365 产品,应根据采购渠道选择数据源:
- CSP 渠道:使用 Partner Center API 查询产品目录、订单、计费明细和发票。能否访问取决于合作伙伴身份、客户关系和授权范围。
- 企业协议或 Microsoft Customer Agreement:检查对应计费账户下可用的 Cost Management 数据、发票和导出功能。具体能获取哪些字段,取决于合同类型和调用方权限。
- 仅需许可证库存:使用 Microsoft Graph
/subscribedSkus和用户许可证相关接口。 - 仅需 Azure 公开价格:使用 Azure Retail Prices API,但不能把这些数据当作最终发票金额。
- 需要准确的应付金额:以实际账单、发票或采购渠道提供的计费 API 为准,不能用公开单价乘以
consumedUnits代替。
权限与实现注意事项
-
Graph 和 Azure Resource Manager 使用不同的访问令牌:
https://graph.microsoft.com/.default https://management.azure.com/.default -
调用
/subscribedSkus需要配置相应的 Microsoft Graph 权限。使用应用权限模式时,还要完成管理员同意。 -
Cost Management API 由 Azure RBAC 控制。应用或托管身份必须在目标订阅、资源组或计费范围内拥有成本读取权限,例如适用范围内的
Cost Management Reader。 -
Azure
subscriptionId不是 Microsoft 365 许可证订阅 ID。即使两者属于同一个 Entra ID 租户,也不能据此逐项映射。 -
ActualCost、摊销成本、未开票成本和最终发票金额可能不同。制作财务报表时,需要明确成本类型、币种和账期。 -
API 版本和 Cost Management 支持的维度可能发生调整。生产代码应根据目标云环境和计费协议核对当前的官方接口定义,并妥善处理分页、限流、空数据和权限错误。
最终应根据许可证的实际采购渠道选择接口:Azure 内产生的费用使用 Cost Management API;Microsoft 365 许可证数量使用 Graph API;Microsoft 365 的准确价格和发票则通过相应的商业计费或合作伙伴接口查询。
备注:内容仅供参考。
