Security boundary
Reconcile canonical data directly
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'Read New API locally, then call the hosted API
This script reads the native API on your machine. The administrator token is sent only to NEW_API_URL and never enters the PayAPIKey request body.
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);NEW_API_URL='https://gateway.example.com' \
NEW_API_ACCESS_TOKEN='replace-locally' \
NEW_API_USER_ID='1' \
node connector.mjsOne API and one-hub use the same pattern: change connector to one-api or one-hub and verify the compatibility path against the deployed version first.
Add upstream cost and payment funding
The normalization endpoint converts downstream log fields. Build upstream provider charges and payment-provider settlements from their respective exports, then merge them into the canonical object.
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 aggregate reads and file import
No VoAPI request-level live-read contract is claimed
Minimum downstream CSV columns: requestId, occurredAt, model, status, chargedAmount. Minimum upstream columns: occurredAt, model, status, costAmount; requestId is recommended. Minimum payment columns: transactionId, occurredAt, status, amount.
Open the browser workbench