特定 Subscription 无法获取 Billing Data
结论
特定 Subscription 返回 pricing costs: null,通常不是请求格式有误,而是该订阅在计费关系、报价类型、权限、生命周期状态或后端数据同步方面与其他订阅不同。
https://s2.billing.ext.azure.com/api/Billing/Subscription/GetSpecsCosts?api-version=2019-01-14 是 Azure Portal 使用的内部接口,不属于稳定、公开的计费 API。HTTP 请求成功,只能说明接口完成了处理,不能保证该 Subscription 有可返回的定价数据。null 也不代表费用为零。
可能原因
1. Subscription 使用了不同的计费模式
异常订阅可能属于不同的计费体系或 Offer,例如:
- Enterprise Agreement(EA)
- Microsoft Customer Agreement(MCA)
- Cloud Solution Provider(CSP)
- Microsoft Online Subscription Program(MOSP)
- Visual Studio、Azure Sponsorship、Free Trial 等权益型订阅
- 内部、测试或特殊报价订阅
部分报价的价格由企业协议、合作伙伴或特定 Billing Profile 管理,Portal 内部接口未必能在 Subscription 层直接返回价格。
可以对比正常订阅和异常订阅的以下信息:
- Billing account
- Billing profile
- Invoice section
- Offer 或 Subscription type
- Enrollment account
- Partner 管理关系
- Billing currency 和市场区域
2. 当前用户只有资源权限,没有账单权限
Azure RBAC 权限与账单权限并不完全相同。用户即使是 Subscription 的 Owner 或 Contributor,也未必有权查看协议价格、发票或成本数据。
常见情况包括:
- 只有 Subscription RBAC 角色,没有 Billing scope 角色
- EA 中未启用 Account Owner 或 Department Administrator 的费用查看权限
- MCA 中未获得 Billing account、Billing profile 或 Invoice section 的访问权限
- CSP Subscription 的成本由合作伙伴管理
- 租户策略禁止显示费用或协议价格
权限不足不一定会返回 403。Portal 的聚合接口可能仍然返回成功响应,只是将价格字段留空。
3. Subscription 没有可匹配的价格数据
以下状态可能导致暂时没有价格可返回:
- Subscription 刚创建
- Subscription 刚转移到其他租户或 Billing account
- 最近更换了 Offer、协议或 Billing Profile
- 订阅尚未产生计量用量
- 用量数据已经产生,但尚未完成计费处理
- 资源使用发生在尚未结算的时间窗口
- Subscription 已禁用、取消、过期或处于异常状态
Azure 的资源部署、用量采集、价格匹配和成本计算并非同一条实时链路。因此,新订阅或刚完成计费关系变更的订阅可能出现同步延迟。具体延迟时间取决于计费类型,无法只通过这个内部接口判断。
4. 资源或计量项无法匹配价格
如果接口返回规格价格,而异常订阅使用了特殊资源、区域或计费条款,后端可能找不到适用的价格。例如:
- 协议中存在自定义折扣或私有价格
- 使用了预留实例、Savings Plan、Azure Hybrid Benefit 等优惠
- Marketplace 项目由第三方定价
- 资源位于特殊区域
- Meter、SKU、currency 或 billing market 无法正确匹配
- 费用由其他 Billing scope 承担
这种情况下,可能只有部分规格的价格为空,也可能整个 pricing costs 都为空。
5. Portal 内部接口存在兼容性或后端数据问题
该域名和 API 路径用于 Portal 场景,可能依赖:
- Portal 当前上下文
- 特定请求头
- 登录令牌中的 tenant 和 billing claims
- Portal 前端版本
- 后端功能开关
- Subscription 与 Billing scope 的内部映射
即使相同调用对其他 Subscription 有效,也不能据此认定所有计费类型都受支持。如果官方 Cost Management 页面也无法显示该订阅的成本,问题更可能出在权限、计费配置或后端数据上。如果官方页面显示正常,而该接口返回 null,则更可能是内部接口的行为差异。
排查步骤
1. 确认 Subscription 状态
在 Azure Portal 的 Subscriptions 页面检查:
- Subscription 是否为
Enabled - 是否刚创建、转移或更改计费关系
- Tenant ID 是否与当前登录租户一致
- Billing account 或 Offer 是否与正常订阅相同
也可以通过 Azure CLI 查看基础信息:
az account show --subscription "<subscription-id>"
重点检查 id、tenantId、state 和用户上下文。该命令只能确认订阅状态,不能证明用户具有账单访问权限。
2. 在 Cost Management 中验证
切换到异常 Subscription 对应的计费范围,检查:
- Cost analysis 是否能显示成本
- Invoices 或 Usage 是否可访问
- 时间范围内是否存在实际用量
- 是否出现权限或数据不可用提示
- 切换到 Billing account、Billing profile 或 Invoice section 后是否能够查看成本
如果 Cost Management 同样没有数据,应先处理计费范围、权限或数据同步问题,不要继续依赖 GetSpecsCosts。
3. 对比正常和异常请求
在浏览器开发者工具的 Network 面板中,分别捕获两个 Subscription 的请求,然后对比:
- Subscription ID
- Tenant 上下文
- 请求头
- 响应正文
- Billing scope
- Currency
- Locale
- Portal 是否发起了额外的前置请求
- 是否存在被忽略的警告或错误字段
不要在工单、日志或聊天中公开 Authorization、Cookie、访问令牌或完整账户信息。
4. 检查账单访问权限
请 Billing account 管理员确认当前用户是否具备对应范围的成本查看权限。仅在 Subscription 上添加 Reader、Contributor 或 Owner,不一定能解决问题。
如果启用了成本可见性策略,还需确认该 Subscription 所属协议允许用户查看费用。具体角色名称和配置入口会因 EA、MCA、CSP 等计费体系而异。
5. 使用受支持的 API 验证成本数据
生产集成应优先使用 Azure Cost Management 的公开 API,不要使用 Portal 内部接口。例如,可以通过 Cost Management Query API 查询 Subscription 范围内的实际成本:
POST https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.CostManagement/query?api-version={supported-api-version}
Authorization: Bearer <access-token>
Content-Type: application/json
请求体示例:
{
"type": "ActualCost",
"timeframe": "MonthToDate",
"dataset": {
"granularity": "Daily",
"aggregation": {
"totalCost": {
"name": "Cost",
"function": "Sum"
}
}
}
}
api-version 应从当前 Azure Cost Management 官方文档或资源提供程序元数据中选择受支持的版本,不要直接照搬 Portal 内部接口使用的 2019-01-14。
调用后需要区分空数据、权限错误和接口失败:
async function querySubscriptionCost(subscriptionId, apiVersion, token) {
const url =
`https://management.azure.com/subscriptions/${encodeURIComponent(subscriptionId)}` +
`/providers/Microsoft.CostManagement/query?api-version=${encodeURIComponent(apiVersion)}`;
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
type: "ActualCost",
timeframe: "MonthToDate",
dataset: {
granularity: "Daily",
aggregation: {
totalCost: {
name: "Cost",
function: "Sum"
}
}
}
})
});
const body = await response.json();
if (!response.ok) {
throw new Error(
`Cost Management query failed: ${response.status} ${JSON.stringify(body)}`
);
}
const rows = body?.properties?.rows;
if (!Array.isArray(rows) || rows.length === 0) {
return {
status: "no-data",
message: "请求成功,但当前范围和时间段没有可返回的成本数据。"
};
}
return {
status: "ok",
data: body
};
}
该查询返回的是已经处理过的成本数据,不是所有 SKU 的公开目录价格。如果需要查询零售价格,应使用 Azure Retail Prices API。如果需要查询客户协议价格,则应使用与相应 Billing account 类型匹配且受支持的计费 API。
6. 必要时提交 Azure 支持请求
如果同时满足以下条件,建议提交 Billing/Cost Management 支持请求:
- Subscription 状态正常
- 当前用户已获得正确的 Billing scope 权限
- Cost Management 在合理时间后仍无数据
- 相同计费类型的其他 Subscription 工作正常
- 异常订阅存在实际用量
- 问题在不同浏览器或用户下均能复现
提交时提供以下脱敏信息:
- Subscription ID
- Tenant ID
- Billing account 类型
- 问题首次出现的时间及时区
- Portal Correlation ID、Request ID
- 正常与异常响应的结构差异
- 是否发生过 Subscription 转移或协议变更
注意事项
- 不要把
pricing costs: null当作费用为0,两者含义不同。 - 不要在生产程序中依赖
billing.ext.azure.com下的 Portal 内部 API,其结构、权限要求和可用性都可能变化。 - Retail Prices、协议价格、实际成本和预测成本是不同的数据,不能相互替代。
- 比较 Subscription 时,应确保时间范围、币种、Billing scope、权限和计费模式一致。
- 如果 Subscription 由 CSP 合作伙伴管理,最终客户可能无法直接取得合作伙伴结算价格,需要联系合作伙伴确认。
- 如果问题只出现在刚创建或刚转移的 Subscription 上,可以先等待计费数据完成处理。如果长时间未恢复,应通过 Azure 支持核查后端映射。
备注:内容仅供参考。
