PAYATHON 2026

如何通过 REST API 获取 Azure 单个资源的当前支出?

支付小周

结论

Azure 没有提供按单个资源实时查询当前支出的计费接口。资源费用需要经过用量采集、计价和入账,无法像 Azure Monitor 指标那样实时更新。

可以调用 Azure Cost Management Query API,在订阅或资源组范围内查询成本,再用 ResourceId 筛选指定资源。查询结果是该资源本月至今已经入账的累计费用,也就是最新可用成本,并非实时金额。

为什么不能实时获取

Azure 资源产生用量后,费用通常要经过以下处理:

  1. 资源服务上报用量。
  2. Azure 根据计费规则、合同价格、折扣和预留实例等信息计算费用。
  3. 成本数据进入 Cost Management。
  4. Query API、Cost Details 或成本导出功能才能查询这些数据。

各项服务上报用量的频率不同,成本数据可能延迟数小时,个别费用的延迟时间还会更长。即使接口返回了当月累计值,最近几分钟或几小时产生的用量也可能尚未计入。

有些费用也无法准确归属到单个资源 ID。例如,以下费用可能没有对应的 ResourceId,也可能记录在其他关联资源上:

  • 带宽、共享网络或跨区域流量费用
  • Reservation 或 Savings Plan 相关费用
  • Marketplace 项目
  • 支持计划、税费和账单级调整
  • 某些经典资源或共享平台服务
  • 由多个资源共同产生的费用

因此,按资源汇总的金额不一定与订阅总费用完全对应。

使用 Cost Management Query API

一般可以在订阅范围发起查询,并使用完整资源 ID 过滤结果:

POST https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.CostManagement/query?api-version=2023-03-01
Authorization: Bearer {accessToken}
Content-Type: application/json

请求体示例:

{
  "type": "ActualCost",
  "timeframe": "MonthToDate",
  "dataset": {
    "granularity": "None",
    "aggregation": {
      "totalCost": {
        "name": "PreTaxCost",
        "function": "Sum"
      }
    },
    "filter": {
      "dimensions": {
        "name": "ResourceId",
        "operator": "In",
        "values": [
          "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example-rg/providers/Microsoft.Compute/virtualMachines/example-vm"
        ]
      }
    }
  }
}

各字段的含义如下:

  • type: "ActualCost" 查询实际成本。
  • timeframe: "MonthToDate" 查询当前自然月至今。
  • PreTaxCost 表示税前成本。
  • ResourceId 必须填写完整的 Azure Resource Manager 资源 ID。
  • granularity: "None" 只返回累计值,不按天拆分。

目标云环境是否支持该 api-version,需要以对应环境的 Azure REST API 文档为准。

也可以使用 curl 调用:

curl --request POST \
  "https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/providers/Microsoft.CostManagement/query?api-version=2023-03-01" \
  --header "Authorization: Bearer ${ACCESS_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{
    "type": "ActualCost",
    "timeframe": "MonthToDate",
    "dataset": {
      "granularity": "None",
      "aggregation": {
        "totalCost": {
          "name": "PreTaxCost",
          "function": "Sum"
        }
      },
      "filter": {
        "dimensions": {
          "name": "ResourceId",
          "operator": "In",
          "values": [
            "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example-rg/providers/Microsoft.Compute/virtualMachines/example-vm"
          ]
        }
      }
    }
  }'

返回结果会将列定义与行数据分开,结构类似:

{
  "properties": {
    "columns": [
      {
        "name": "PreTaxCost",
        "type": "Number"
      },
      {
        "name": "Currency",
        "type": "String"
      }
    ],
    "rows": [
      [
        12.34,
        "USD"
      ]
    ]
  }
}

其中,12.34 USD 是该资源在查询时间范围内已经进入 Cost Management 的累计税前成本。

Node.js 示例

以下示例使用 @azure/identity 获取 Azure Resource Manager 访问令牌:

import { DefaultAzureCredential } from "@azure/identity";

const subscriptionId = process.env.AZURE_SUBSCRIPTION_ID;
const resourceId =
  "/subscriptions/00000000-0000-0000-0000-000000000000" +
  "/resourceGroups/example-rg" +
  "/providers/Microsoft.Compute/virtualMachines/example-vm";

const credential = new DefaultAzureCredential();
const token = await credential.getToken(
  "https://management.azure.com/.default"
);

const endpoint =
  `https://management.azure.com/subscriptions/${subscriptionId}` +
  "/providers/Microsoft.CostManagement/query" +
  "?api-version=2023-03-01";

const response = await fetch(endpoint, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token.token}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    type: "ActualCost",
    timeframe: "MonthToDate",
    dataset: {
      granularity: "None",
      aggregation: {
        totalCost: {
          name: "PreTaxCost",
          function: "Sum"
        }
      },
      filter: {
        dimensions: {
          name: "ResourceId",
          operator: "In",
          values: [resourceId]
        }
      }
    }
  })
});

if (!response.ok) {
  throw new Error(
    `Cost query failed: ${response.status} ${await response.text()}`
  );
}

const result = await response.json();
const columns = result.properties?.columns ?? [];
const rows = result.properties?.rows ?? [];

if (rows.length === 0) {
  console.log("当前时间范围内没有可用的成本数据");
} else {
  const record = Object.fromEntries(
    columns.map((column, index) => [
      column.name,
      rows[0][index]
    ])
  );

  console.log(
    `已入账成本:${record.PreTaxCost} ${record.Currency ?? ""}`
  );
}

不要假定返回列的顺序固定。解析 rows 时,应根据 properties.columns 中的列名找到对应的值。

计费周期不是自然月时

MonthToDate 指自然月至今,不一定与合同账单周期一致。如果需要严格按账单周期统计,可以将 timeframe 改为 Custom,并提供起止时间:

{
  "type": "ActualCost",
  "timeframe": "Custom",
  "timePeriod": {
    "from": "2026-09-01T00:00:00Z",
    "to": "2026-09-13T23:59:59Z"
  },
  "dataset": {
    "granularity": "None",
    "aggregation": {
      "totalCost": {
        "name": "PreTaxCost",
        "function": "Sum"
      }
    },
    "filter": {
      "dimensions": {
        "name": "ResourceId",
        "operator": "In",
        "values": [
          "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example-rg/providers/Microsoft.Compute/virtualMachines/example-vm"
        ]
      }
    }
  }
}

日期边界需要根据实际账单周期和时区确定,尤其要留意 UTC 与本地时间的差异。

权限和结果解释

调用接口的身份必须有权读取对应范围内的成本数据。例如,在订阅范围查询时,需要授予合适的 Cost Management 读取权限。只有资源本身的读取权限,不一定能读取订阅级成本。

使用查询结果时,还要注意以下情况:

  • 新建资源暂时查不到费用,通常说明成本还没有入账,不一定表示费用为零。
  • 已删除的资源仍可能在后续查询中出现延迟入账的费用。
  • ActualCost 与 AmortizedCost 的计算口径不同。涉及 Reservation 或 Savings Plan 时,需要根据业务用途选择。
  • 查询结果使用账单或成本管理数据中的货币,不能直接假定为美元。
  • 标签无法保证资源唯一性。如果能取得完整资源 ID,应优先使用 ResourceId。
  • 某些子资源没有独立的计费记录,费用可能计入父资源或其他计量项。

如果业务需要按分钟展示费用,只能结合 Azure Monitor 用量指标和相应价格自行估算。这类结果可以用于预算提醒或趋势展示,但不能替代 Azure Cost Management 提供的正式成本数据。

备注:内容仅供参考。