Azure billing rate card api 和 usage api 返回空数据
结论
200 OK 只说明请求通过了身份验证并被 Azure 接收,不代表当前订阅中一定有可返回的计费数据。
如果 Rate Card API 和 Usage API 都返回空数组,问题通常不在日期范围,而在以下方面:
- 当前调用的是旧版
Microsoft.Commerce计费 API,但订阅的计费类型或购买渠道不受该 API 支持。 - Access Token、URL 中的 subscription ID 与实际产生费用的订阅不一致。
- 调用身份只有资源读取权限,没有读取成本或计费数据所需的权限。
Rate Card API的报价、币种、区域等过滤条件与订阅不匹配。- VM 尚未产生已入账的用量,或者用量数据仍在计费系统中聚合。
- 响应包含分页信息,但客户端没有继续请求
nextLink。 - 订阅采用 CSP、Microsoft Customer Agreement(MCA)等较新的计费模型,却仍在调用面向旧计费模型的接口。
先确认订阅的计费账户类型,再决定继续使用旧版 Microsoft.Commerce API,还是改用 Azure Cost Management、Consumption 或新版价格 API。
VM 存在不等于已有计费数据
资源管理数据和计费数据来自不同的系统。
VM 出现在资源组中,只能证明 Azure Resource Manager 中存在该资源。以下情况都可能导致暂时查不到用量:
- VM 刚创建,计费数据还没有聚合完成。
- VM 已停止并解除分配,没有继续产生计算用量。
- 使用了免费额度、赠送额度或特殊订阅。
- 实际费用记录在另一个订阅或计费范围下。
- 查询时间使用了错误的时区,或者所查时间段尚未完成结算。
计费数据通常不是实时生成的,延迟时间取决于具体服务和计费体系。因此,不能仅凭 VM 已经存在,就认定 API 应立即返回记录。
先核对实际调用的 API
旧版接口通常类似:
GET https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Commerce/usageAggregates
?reportedStartTime=2024-01-01T00:00:00Z
&reportedEndTime=2024-01-31T00:00:00Z
&aggregationGranularity=Daily
&showDetails=true
&api-version=2015-06-01-preview
Rate Card 接口通常类似:
GET https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Commerce/rateCard
?$filter=OfferDurableId eq '{offerId}'
and Currency eq 'USD'
and Locale eq 'en-US'
and RegionInfo eq 'US'
&api-version=2016-08-31-preview
这些 Microsoft.Commerce 接口较旧,而且依赖订阅的 offer 和计费渠道。即使 HTTP 请求成功,只要订阅不适用,也可能返回空集合。接口返回 200,不能证明该接口支持当前订阅。
排查订阅和 Token 是否对应
先检查当前登录上下文:
az account show \
--query "{subscriptionId:id, subscriptionName:name, tenantId:tenantId, state:state}" \
--output json
确认请求 URL 中的 {subscriptionId} 与这里显示的 subscriptionId 完全一致。
Token 必须由 Azure Resource Manager 签发,audience 应对应:
https://management.azure.com/
可以使用 Azure CLI 获取测试 Token:
az account get-access-token \
--resource https://management.azure.com/ \
--query accessToken \
--output tsv
如果应用注册在多个租户中,还要确认:
- Token 由订阅所属租户签发。
- 服务主体确实存在于该租户中。
- 请求没有误用另一个租户或订阅的缓存 Token。
- VM 所在订阅与 API URL 中的订阅一致。
还可以直接查询 VM 的订阅归属,进行交叉检查:
az vm show \
--resource-group <resource-group> \
--name <vm-name> \
--query id \
--output tsv
返回的资源 ID 中应包含:
/subscriptions/<subscription-id>/
检查计费与成本权限
能够读取 VM,不代表能够读取成本数据。Reader 权限主要用于资源管理,某些计费接口还要求调用身份拥有成本管理权限或相应计费范围的权限。
根据订阅的计费模型,调用身份通常需要具备以下权限之一:
- 在订阅范围拥有允许查看成本数据的角色,例如
Cost Management Reader。 - 在对应 billing account、billing profile 或 invoice section 范围拥有读取权限。
- 如果使用自定义角色,该角色需要包含目标 API 要求的读取操作。
- 租户或计费账户中的成本可见性策略允许订阅用户查看费用。
可以检查订阅范围内的角色分配:
az role assignment list \
--assignee <principal-object-id> \
--scope /subscriptions/<subscription-id> \
--output table
权限不足时更常见的响应是 401 或 403,但部分旧接口或特殊计费渠道也可能只返回空结果。因此,收到 200 OK 并不能排除授权范围问题。
检查 Rate Card 的过滤条件
Rate Card 查询不会根据资源组或现有 VM 返回价格,而是根据以下计费属性返回价目表:
OfferDurableIdCurrencyLocaleRegionInfo
例如:
$filter=OfferDurableId eq 'MS-AZR-0003P'
and Currency eq 'USD'
and Locale eq 'en-US'
and RegionInfo eq 'US'
任一属性与订阅不匹配,都可能得到空结果。不要根据门户显示语言或 VM 所在区域推断这些值:
RegionInfo表示计费市场,不一定等于 VM 的 Azure region。Currency必须与计费账户使用的币种一致。OfferDurableId必须对应订阅的实际 offer,不能直接照搬其他示例中的值。- 企业协议、CSP、MCA 和特殊优惠订阅可能没有适用于该旧接口的 offer 组合。
如果无法通过当前订阅或计费合同确认这些值,就不应反复猜测参数。请让订阅管理员在 Azure 门户的计费属性中核对购买渠道、计费账户和报价类型。
检查 Usage API 的时间和分页
查询时间应满足以下条件:
- 使用 UTC ISO 8601 格式。
reportedStartTime早于reportedEndTime。- 结束时间不能是未来时间。
- 时间范围覆盖 VM 实际运行并产生用量的日期。
- 必要时先查询已经完整结束的日期,例如前一天或更早。
- 不要一次请求过大的时间范围,可以先用较小的时间窗口定位问题。
示例:
curl --get \
"https://management.azure.com/subscriptions/<subscription-id>/providers/Microsoft.Commerce/usageAggregates" \
--data-urlencode "reportedStartTime=2024-01-01T00:00:00Z" \
--data-urlencode "reportedEndTime=2024-01-08T00:00:00Z" \
--data-urlencode "aggregationGranularity=Daily" \
--data-urlencode "showDetails=true" \
--data-urlencode "api-version=2015-06-01-preview" \
-H "Authorization: Bearer <access-token>"
如果响应中包含非空的 nextLink,应按原值继续请求,不要自行重新编码或拼接其中的查询参数:
import requests
url = (
"https://management.azure.com/subscriptions/<subscription-id>"
"/providers/Microsoft.Commerce/usageAggregates"
)
params = {
"reportedStartTime": "2024-01-01T00:00:00Z",
"reportedEndTime": "2024-01-08T00:00:00Z",
"aggregationGranularity": "Daily",
"showDetails": "true",
"api-version": "2015-06-01-preview",
}
headers = {
"Authorization": "Bearer <access-token>",
}
items = []
while url:
response = requests.get(
url,
params=params,
headers=headers,
timeout=60,
)
response.raise_for_status()
body = response.json()
items.extend(body.get("value", []))
url = body.get("nextLink")
params = None # nextLink 已包含后续请求参数
print(f"records: {len(items)}")
题目中的 nextLink 还出现了异常字符和弯引号。如果这些字符不是复制或显示过程造成的,应检查客户端的字符编码、JSON 解析和日志输出。响应必须交给 JSON 解析器读取,不能依赖字符串截取。
使用与当前计费模型匹配的接口
如果订阅采用较新的计费体系,建议使用 Cost Management 或 Consumption API,不要继续依赖旧版 Microsoft.Commerce 接口。
查询订阅范围成本的请求形式例如:
POST https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.CostManagement/query?api-version=<supported-api-version>
Authorization: Bearer <token>
Content-Type: application/json
请求体示例:
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "2024-01-01T00:00:00Z",
"to": "2024-01-08T00:00:00Z"
},
"dataset": {
"granularity": "Daily",
"aggregation": {
"totalCost": {
"name": "PreTaxCost",
"function": "Sum"
}
}
}
}
api-version、可用指标和查询范围会随 Azure API 及计费模型变化。应根据当前 Azure REST API 文档,或订阅实际返回的 provider metadata,选择受支持的版本,不能假定旧示例中的版本适用于所有订阅。
如果需要的是公开零售价,而不是订阅合同价,可以考虑 Azure Retail Prices API。公开零售价不等于企业协议、CSP 折扣或其他合同下的实际结算价格。
推荐的定位顺序
按以下顺序排查通常更快:
- 确认 VM 资源 ID 中的 subscription ID。
- 确认 Token 的 tenant 和 audience。
- 确认调用身份在订阅及相应计费范围内拥有成本读取权限。
- 在 Azure 门户中确认订阅的计费账户类型和购买渠道。
- 查询已经完整结束的历史日期,并正确处理
nextLink。 - 核对 Rate Card 的 offer、币种、locale 和计费市场。
- 使用同一身份测试 Cost Management Query API。
- 如果 Cost Management 能返回数据,而
Microsoft.Commerce始终为空,基本可以判断旧接口与当前计费模型不兼容。 - 如果门户中已经有结算成本,权限也正确,但新旧接口都返回空结果,应记录 request URL、时间范围、subscription ID、tenant ID,以及响应头中的 request ID,然后提交 Azure 支持请求。
不要在日志或支持材料中暴露 Access Token、客户端密钥或完整授权头。
备注:内容仅供参考。
