调用示例

从中转站日志到差异报告

可以复制的 cURL 和本地 Node.js sidecar 示例。

安全边界

PayAPIKey 托管接口不接收中转站管理员 Token、渠道 Key、Cookie 或会话。原生 API 读取必须在你控制的本地 connector/sidecar 中完成;托管端只接收去敏后的计费元数据。

规范数据直接对账

Shell
curl -s https://www.payapikey.com/api/v1/examples/reconciliation \
  | jq '.data' > reconciliation.json

curl -s -X POST https://www.payapikey.com/api/v1/reconcile \
  -H 'Content-Type: application/json' \
  --data-binary @reconciliation.json \
  | jq '.data.summary, .data.findings'

本地读取 New API,再调用托管接口

以下脚本在本机读取原生 API。管理员 Token 只发往 NEW_API_URL,不会进入 PayAPIKey 请求体。

connector.mjs
const gatewayUrl = process.env.NEW_API_URL;
const statusResponse = await fetch(new URL("/api/status", gatewayUrl));
const gatewayResponse = await fetch(
  new URL("/api/log/?type=2&p=1&page_size=100", gatewayUrl),
  {
    headers: {
      Authorization: `Bearer ${process.env.NEW_API_ACCESS_TOKEN}`,
      "New-Api-User": process.env.NEW_API_USER_ID,
    },
  },
);

if (!statusResponse.ok || !gatewayResponse.ok) throw new Error("Gateway read failed");
const status = await statusResponse.json();
const quotaPerUnit = Number(status?.data?.quota_per_unit);
if (!(quotaPerUnit > 0)) throw new Error("Missing quota_per_unit");

const privateFields = new Set([
  "key", "secret", "api_key", "access_token", "authorization",
  "password", "cookie", "session", "prompt", "completion",
  "content", "messages", "request_body", "response_body",
]);
function stripPrivate(value) {
  if (Array.isArray(value)) return value.map(stripPrivate);
  if (!value || typeof value !== "object") return value;
  return Object.fromEntries(
    Object.entries(value)
      .filter(([key]) => !privateFields.has(key.toLowerCase()))
      .map(([key, child]) => [key, stripPrivate(child)]),
  );
}
const logs = stripPrivate(await gatewayResponse.json());

// Submit billing metadata only. Never add credentials, prompts, or completions.
const normalizedResponse = await fetch(
  "https://www.payapikey.com/api/v1/normalize",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      connector: "new-api", currency: "USD", quotaPerUnit, logs,
    }),
  },
);

if (!normalizedResponse.ok) throw new Error(await normalizedResponse.text());
const { data: canonical } = await normalizedResponse.json();

const reportResponse = await fetch("https://www.payapikey.com/api/v1/reconcile", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(canonical),
});

if (!reportResponse.ok) throw new Error(await reportResponse.text());
const { data: report } = await reportResponse.json();
console.log(report.summary);
console.table(report.findings);
Shell
NEW_API_URL='https://gateway.example.com' \
NEW_API_ACCESS_TOKEN='replace-locally' \
NEW_API_USER_ID='1' \
node connector.mjs

One API 或 one-hub 可使用同一模式,把 connector 改为 one-api 或 one-hub,并先核对部署版本的兼容路径。

加入上游成本与支付账

规范化端点负责下游日志字段转换。上游供应商账单和支付服务商结算应由各自导出文件形成,再合并到 canonical 对象。

JavaScript
canonical.upstreamCharges = upstreamExport.map(mapUpstreamCharge);
canonical.payments = processorExport.map(mapPaymentSettlement);

// Payment captures fund the wallet; they are not added to consumption revenue.
const response = await fetch("https://www.payapikey.com/api/v1/reconcile", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(canonical),
});

VoAPI 聚合读取与文件导入

不声明 VoAPI 请求级实时读取

VoAPI v2 的 /api/dash/statistics 可在本地读取聚合统计,但无法提供 Request ID 级结论。精确对账请导出计费元数据并分别导入下游、上游或支付 CSV/JSON;不要把 One API 兼容路径当作 VoAPI v2 接口。

下游 CSV 最小列:requestId, occurredAt, model, status, chargedAmount。上游 CSV 最小列:occurredAt, model, status, costAmount;建议同时提供 requestId。支付 CSV 最小列:transactionId, occurredAt, status, amount。

打开浏览器对账工具