← back to Open Seo
scripts/dataforseo-account-usage.ts
117 lines
import process from "node:process";
import type { AppendixStatisticsRatesDataInfo } from "dataforseo-client";
import { fetchUserData } from "@/server/lib/dataforseo/appendix";
import { loadLocalEnv, parseArgs } from "./cli-utils";
loadLocalEnv();
const args = parseArgs(process.argv.slice(2));
await main();
/**
* Inspect real DataForSEO account spend via the FREE GET
* /v3/appendix/user_data endpoint. Unlike the other billing:* scripts, this
* makes NO billable calls — it just reads the numbers DataForSEO already
* tracks: lifetime deposit, remaining balance, and per-function spend for the
* rolling day / minute window.
*
* Usage: pnpm billing:usage [--json=true]
*/
async function main() {
if (!process.env.DATAFORSEO_API_KEY) {
printUsageAndExit("Missing DATAFORSEO_API_KEY.");
}
const account = await fetchUserData();
if (!account) {
console.error(
"DataForSEO returned no account data (empty result). Check the API key.",
);
process.exit(1);
}
if (args.json === "true") {
console.log(JSON.stringify(account, null, 2));
return;
}
const money = account.money;
console.log("DataForSEO account usage");
console.log("========================");
console.log(`Login: ${account.login ?? "(unknown)"}`);
if (account.timezone) console.log(`Timezone: ${account.timezone}`);
console.log(`Deposited total: ${formatUsd(money?.total)}`);
console.log(`Balance left: ${formatUsd(money?.balance)}`);
printFunctionTable("Spend by function — rolling DAY", money?.statistics?.day);
printFunctionTable(
"Spend by function — rolling MINUTE",
money?.statistics?.minute,
);
}
// DataForSEO groups spend under `total_<function>` keys on each statistics
// window. Field names mirror the SDK's AppendixStatisticsRatesDataInfo.
const FUNCTION_TOTALS: ReadonlyArray<{ label: string; key: string }> = [
{ label: "serp", key: "total_serp" },
{ label: "keywords_data", key: "total_keywords_data" },
{ label: "dataforseo_labs", key: "total_dataforseo_labs" },
{ label: "backlinks", key: "total_backlinks" },
{ label: "on_page", key: "total_on_page" },
{ label: "business_data", key: "total_business_data" },
{ label: "domain_analytics", key: "total_domain_analytics" },
{ label: "merchant", key: "total_merchant" },
{ label: "app_data", key: "total_app_data" },
{ label: "content_analysis", key: "total_content_analysis" },
{ label: "content_generation", key: "total_content_generation" },
{ label: "appendix", key: "total_appendix" },
];
function printFunctionTable(
heading: string,
stats: AppendixStatisticsRatesDataInfo | undefined,
) {
console.log("");
console.log(heading);
console.log("-".repeat(heading.length));
if (!stats) {
console.log("(no data)");
return;
}
if (stats.value) console.log(`Window: ${stats.value}`);
const rows = FUNCTION_TOTALS.map(({ label, key }) => ({
function: label,
spend: readNumber(stats[key]),
})).filter((row) => row.spend > 0);
if (rows.length === 0) {
console.log("(no spend recorded in this window)");
} else {
for (const row of rows) {
console.log(` ${row.function.padEnd(20)} ${formatUsd(row.spend)}`);
}
}
// Prefer the API's own grand total; fall back to the summed rows.
const total =
readNumber(stats.total) || rows.reduce((sum, row) => sum + row.spend, 0);
console.log(` ${"TOTAL".padEnd(20)} ${formatUsd(total)}`);
}
function readNumber(value: unknown): number {
return typeof value === "number" && Number.isFinite(value) ? value : 0;
}
function formatUsd(value: number | undefined): string {
if (typeof value !== "number" || !Number.isFinite(value)) return "$0.00";
return `$${value.toFixed(2)}`;
}
function printUsageAndExit(message: string): never {
console.error(message);
console.error("Usage: pnpm billing:usage [--json=true]");
process.exit(1);
}