One bucket you own. Small collectors you deploy. A browser page that reads them. You run every piece, and nothing is sent anywhere else.
About 30 minutes for your first vendor, minutes for each one after.
This guide walks you through:
npx, which runs Cloudflare's wrangler CLI as npx wrangler β no separate install. Sign in once with npx wrangler login.Workers run your collectors on a schedule; R2 is Cloudflare's S3-compatible object storage with zero egress β which is exactly why your browser can read it directly, with no server of ours in the middle. The $5/mo Workers Paid plan covers both; a real multi-vendor fleet uses under 1% of what it includes, so there's no usage bill to watch.
One R2 bucket holds every vendor, each under its own prefix (aws/, fireworks/, β¦).
npx wrangler r2 bucket create mycloudbills
mycloudbills is just the default β bucket names are unique within your own account, so any name works and won't collide with anyone else's.
A collector is a small folder: a WASM Worker on a Cron Trigger that reads one vendor's billing API and writes a daily rollup to your bucket. Pick a vendor and run three commands:
cd fireworks2parquet/worker
npx wrangler secret put FIREWORKS_API_KEY
npx wrangler deploy
Then run it once instead of waiting for the cron. wrangler deploy prints the Worker's URL β just curl that. It looks like:
curl https://fireworks2parquet.<your-workers-name>.workers.dev
<your-workers-name> is your account's workers.dev name β you pick it once per Cloudflare account (e.g. acme β acme.workers.dev), and every Worker you deploy lives under it. It's not per-Worker, so the simplest thing is to copy the exact URL from the wrangler deploy output.
Available collectors
| Collector | Reads | Credential |
|---|---|---|
fireworks2parquet | Fireworks usage + rated cost | FIREWORKS_API_KEY + FIREWORKS_ACCOUNT_ID var |
cartesia2parquet | Cartesia credits + agents | CARTESIA_ADMIN_API_KEY |
openrouter2parquet | OpenRouter Analytics API | OPENROUTER_API_KEY (management key) |
aws2parquet | AWS Cost Explorer, daily by service | AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY |
cloudflare2parquet | Cloudflare usage β Workers + R2 | CLOUDFLARE_API_TOKEN (Analytics read) + CLOUDFLARE_ACCOUNT_ID var |
twilio2parquet | Twilio Usage Records β SMS, voice, numbers | TWILIO_API_KEY_SID + TWILIO_API_KEY_SECRET (or TWILIO_AUTH_TOKEN) + TWILIO_ACCOUNT_SID var |
openai2parquet | OpenAI org Costs API, daily by line item | OPENAI_ADMIN_KEY (Admin key β Owner/Admin) |
anthropic2parquet | Anthropic Admin Cost Report, daily | ANTHROPIC_ADMIN_KEY β an Admin key (sk-ant-adminβ¦). Requires an Anthropic Team/Organization (convert from an individual account): only then does the Admin keys page exist. Regular (sk-ant-apiβ¦) and service (sk-ant-svcβ¦) keys are rejected by the cost API. |
subscriptions2parquet | Flat subscriptions / seats (ChatGPT Plus, Claude seat, Cursorβ¦) β amortized daily | No key, no API. Edit subscriptions.json (name + usd_per_month), then deploy. Captures the flat SaaS spend that no usage API reports. |
wrangler secret put. Never put them in wrangler.toml, and never commit them. Give each credential the narrowest read scope the vendor allows.The page reads R2 directly from your browser, so the bucket needs two things.
GET, HEAD. Expose ETag, Content-Length, Content-Range, Accept-Ranges.Go to mycloudbills.com/ai-spend.html. Enter your R2 account id, the bucket name, and the read-only Access Key ID + Secret. The page lists every prefix, pulls the Parquet, and does all the math locally. Your keys never leave the tab.
Repeat step 2 for each collector you want. The page picks up new prefixes automatically β no changes to the page, and no changes to the reader.
That's the whole path. Everything below is optional.
$5/month, flat. Run the account on the Workers Paid plan and stop thinking about it. That plan includes 250 Cron Triggers, 10M requests, and 30M CPU-ms per month; a real multi-vendor fleet uses under 1% of it, so you never see a usage bill. Each collector's wrangler.toml caps CPU per run (cpu_ms = 1000), so a bug can't run away. The viewer is free β a static page on R2, zero egress, open it as often as you like.
Edit bucket_name in that worker's wrangler.toml before deploying.
Run make publish to build the wasm and deploy web/ to Cloudflare Pages (it provides the domain, HTTPS, and correct content-types). The web/ files are fully static, so any static host works too.
Deploy aws2parquet/cloudformation.yaml for a read-only ce:Get* user, then enable Cost Explorer once in the console. This adds about $0.30/month β Cost Explorer bills $0.01 per call on a daily cron.
The token needs Account Analytics: Read (not billing). It's usage-forward: you see Workers requests/CPU-ms and R2 storage/ops trending toward the included limits ("am I about to tip into overages?"), with $0 billed until you do.
OpenRouter returns cost by model. session_id is available if you want to break spend down by task/conversation later.
Any vendor with a billing or usage API can become a collector β see COLLECTORS.md for the full recipe and a paste-ready prompt. The only per-vendor work is mapping that API's response to a daily (day, model/service, cost) rollup.
Privacy & the trust model, self-hosting, how it compares to SaaS FinOps tools, and tracking flat subscriptions.
Realistic sample data, no setup β see the viewer before you install anything.
COLLECTORS.md has the recipe, a paste-ready prompt, and the test bar a PR must clear.
AWS, Cloudflare, Fireworks, Cartesia, OpenRouter, OpenAI, Anthropic, and Twilio are trademarks of their respective owners. MyCloudBills is an independent open-source project and is not affiliated with or endorsed by these providers.