提供 client/UI 时如何对 API calls 进行计量与计费?
结论
JavaScript UI component 会调用 API,并不意味着需要另建一套计量系统。所有请求可以统一在服务端或 API Gateway 计量,再通过 tenant_id、application_id、channel 等维度标记来源。
是否采用单独的商业模式,应由产品策略决定,而不是取决于技术调用方式:
- 如果 UI component 只是 API 的另一种入口,可以沿用相同的调用量计费方式。
- 如果 UI component 还提供托管、持续升级、主题定制或技术支持等额外价值,可以单独收取订阅费或设置 SKU。
- 即使两者定价不同,也建议共用一套 usage ledger,避免两套计量数据出现冲突。
为什么不能在 UI component 内计量
浏览器端代码不可信。客户可以修改 JavaScript、绕过计量逻辑,也可以直接调用底层 API,所以计费记录必须由服务端生成。
长期有效的客户 API key 也不能嵌入 UI component,因为浏览器中的凭据很容易被查看和复制。通常的做法是由客户后端签发短期 token,并在其中写入租户、应用和使用渠道等信息。
请求中可以包含类似下面的身份信息:
{
"tenant_id": "customer-123",
"application_id": "portal-web",
"channel": "embedded-ui",
"exp": 1788206400
}
服务端验证 token 后,将用量归属到对应客户。channel 只是计量维度,不能由浏览器传入未经签名、可以随意修改的值。
推荐的计量流程
1. 为客户和调用来源建立稳定标识
至少记录以下信息:
tenant_id:费用归属的客户application_id:调用 API 的应用channel:例如direct-api、embedded-uioperation:执行的业务操作request_id或idempotency_key:用于去重quantity:本次产生的计费单位timestamp:服务端接受或完成请求的时间
不要仅凭 IP 地址、User-Agent 或 Referer 判断客户身份。这些信息既不稳定,也容易伪造。
2. 由客户后端签发短期 token
下面是一个简化的浏览器端示例。生产环境中的 token 应由客户后端获取,API secret 不能保存在前端。
async function loadWidget() {
const tokenResponse = await fetch("/api/service-widget-token", {
method: "POST",
credentials: "same-origin"
});
if (!tokenResponse.ok) {
throw new Error("Unable to obtain widget token");
}
const { accessToken } = await tokenResponse.json();
const widget = new ServiceWidget({
accessToken,
apiBaseUrl: "https://api.example.com"
});
widget.mount(document.querySelector("#service-widget"));
}
客户后端确认当前登录用户有权限后,签发有效期较短的 token。服务提供方的 API 根据 token 中经过签名的 claims 确定费用归属。
3. 在服务端生成不可重复的 usage event
业务请求成功后,由服务端写入计量事件:
app.post("/v1/jobs", authenticate, async (req, res) => {
const result = await createJob(req.body);
await usageLedger.record({
eventId: req.id,
tenantId: req.auth.tenantId,
applicationId: req.auth.applicationId,
channel: req.auth.channel,
operation: "job.create",
quantity: 1,
occurredAt: new Date().toISOString()
});
res.status(201).json(result);
});
这个示例省略了事务、失败恢复和消息队列处理。实际实现必须保证两件事:业务成功但计量暂时写入失败时,系统可以重试;重试时不会重复收费。常见方案包括 transactional outbox、唯一 eventId 和幂等写入。
“一次 API call”需要先定义清楚
按 HTTP 请求次数计费看起来简单,实际很容易产生争议。例如:
- UI component 自动刷新是否收费?
- 网络失败后的重试是否重复收费?
401、429、5xx是否计费?- 浏览器预检请求
OPTIONS是否计费? - UI 为完成一个操作调用了三个内部 API,算一次还是三次用量?
- 缓存命中是否仍然收费?
一般不应将认证失败、限流响应、健康检查、OPTIONS 请求和服务端失败计入费用。对于重试,可以使用 idempotency_key 或业务操作 ID 去重。
如果 UI 中的一个用户动作会触发多个内部请求,按”业务操作”计费通常比按原始 HTTP 请求数更容易解释。例如,一次报告生成可以记录为:
{
"operation": "report.generate",
"quantity": 1
}
不必分别对获取配置、提交任务和轮询状态收费。
是否需要单独定价
常见的定价方式有以下三种。
统一按用量计费
直接 API 和 UI component 使用同一价格,只通过 channel 区分来源。这种方式较为简单,也不会因为客户改变集成方式而产生明显的价格差异。
UI 订阅费加 API 用量费
UI component 收取固定订阅费,底层业务操作仍按用量收费。这种方式适合组件本身还提供持续维护、升级、主题配置或 SLA 等价值的情况。
单独的 UI 套餐
针对 UI 提供席位、月度额度或分级套餐。实际 API 用量仍写入统一账本,再由计费规则进行换算。这种方式适合 UI 用户数与 API 调用量关系稳定,而且客户希望账单更加可预测的场景。
无论采用哪种方式,都应避免对同一个业务操作既收取 UI 使用费,又按其内部 API calls 再次收费。除非合同和账单已经明确说明,两者是独立的收费项目。
注意事项
计量规则应写入客户可见的定价说明,并明确成功状态、重试、缓存、批量请求和超额用量如何处理。账单最好按客户、应用、渠道和操作展示汇总数据,同时保留可审计的原始 usage events。
技术上使用统一的计量体系,商业上则可以按渠道设置不同价格。这样既能支持 UI component 和直接 API integration,也能在以后调整套餐时继续使用现有的认证、计量和审计体系。
备注:内容仅供参考。
