Launch offer: everything is 100% off. Download any skill free, no sign-up. Get the skills
TechFixSkillsBill Spiked
Bill Spiked

Bill Spiked: find what is running up your AI or cloud bill, and stop it

Paste the usage export or alert email and point it at your repo: you get the steps that stop the spend now, the code that matches the spike, and fixes to approve. Before a launch, it scans for uncapped AI calls, open paid endpoints and SMS pumping risks.

Free during launch. Works with Claude Code, Claude.ai, Cursor and Codex.

AgentsClaude Code, Claude.ai, Cursor, Codex
Read-onlyfixes only with your OK
Offlineno network calls, no account
NeedsPython 3.8+ (standard library)

When to use it

Use it in two moments: when the bill has already jumped, and before you launch something that calls a paid API.

What you type is plain English. In an emergency, paste what you have (the alert email, a usage export, a Cost Explorer or billing CSV, a Twilio log, or just a description) and say which repo it is. Before a launch, say "check this repo for cost risks".

Emergency mode answers in this order: stop the spend first, then explain. The skill tells Claude not to explore the repo before you have the steps that stop the bleeding.

How it works

  1. A script runs first. In an emergency Claude saves what you pasted to a temp file and runs scripts/triage.py with it and --repo. It reads the export, finds the provider, the day or hour the spike started and its shape (sudden or steady, more requests or bigger ones, which model, key or project), then scans the repo for code that fits that shape. Before a launch Claude runs scripts/scan.py on the repo instead. Both use only Python's standard library; on the examples below each finished in under half a second.
  2. Stop the bleeding. In an emergency the report starts with numbered steps: the code-level off switch for the matched cause (close the route, disable the trigger, set concurrency to 0), then the provider's own cap or kill switch from references/caps.json. Key rotation goes first only when the data points at a leaked key.
  3. Claude confirms each finding. It opens the file and reads the line before presenting anything. Then it does its own pass with references/review-checklist.md: helpers in other files, auth in middleware, limits set on a shared client, IaC, huge limits that look like caps.
  4. A verdict comes first. Confident findings are ranked by blast radius (how expensive x how unbounded x who can trigger it). Risks the scan could not confirm go under "Check these" instead of being called emergencies. Coverage gaps (Go, Pulumi, unreadable files) are repeated, so a repo is never called clean when part of it was not read.
  5. Fixes are diffs you approve. One diff per fix, worst first. Nothing is applied until you say yes.
  6. A cap checklist closes it. For each paid provider it found, what the provider actually enforces, which settings only send an email, and where to click, each with its source and the date it was checked. The report warns you if that data is more than 90 days old.

The scripts are read-only. The only file they can write is one line of counts to .techfix/ledger.md when run with --ledger, and Claude asks first.

What it checks

AI calls

Who can trigger it

Things that repeat on their own

Infrastructure and metered extras

What it can't see

Worked examples

1. Emergency: an OpenAI bill that went from $1 a day to $1,243

A small Next.js recipe helper on Vercel. Usage was flat for days, then requests jumped about 78x overnight. The chat route has no sign-in, no rate limit and no output cap. The developer exported daily usage from the OpenAI dashboard (key shown masked, as the export does).

app/api/chat/route.ts
export async function POST(req: NextRequest) {
  const { messages } = await req.json();

  const completion = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [
      { role: "system", content: "You are a friendly recipe helper." },
      ...messages,
    ],
  });

  return NextResponse.json({ reply: completion.choices[0].message.content });
Developer
My OpenAI bill went from about a dollar a day to over $1,200 yesterday and it is still going. Here is the usage export. The app is in this repo. What do I do?
Pasted usage export (openai-usage-oct.csv)
date,project,api_key,model,num_model_requests,input_tokens,output_tokens,cost_usd
2026-10-06,recipe-helper,sk-proj-...q7Xa,gpt-4o,412,61800,98900,1.14
2026-10-07,recipe-helper,sk-proj-...q7Xa,gpt-4o,388,58200,93100,1.08
2026-10-08,recipe-helper,sk-proj-...q7Xa,gpt-4o,430,64500,103200,1.19
2026-10-09,recipe-helper,sk-proj-...q7Xa,gpt-4o,401,60150,96240,1.11
2026-10-10,recipe-helper,sk-proj-...q7Xa,gpt-4o,9820,14730000,23568000,272.51
2026-10-11,recipe-helper,sk-proj-...q7Xa,gpt-4o,41250,61875000,99000000,1144.69
2026-10-12,recipe-helper,sk-proj-...q7Xa,gpt-4o,44800,67200000,107520000,1243.20
Script output (trimmed)
Most likely: POST /api/chat in app/api/chat/route.ts:4: public endpoint calls OpenAI with no sign-in or rate limit. OpenAI spike (sudden, more requests).

## Stop the bleeding now (in order)

1. Close the open endpoint now: return 401 for unauthenticated callers in app/api/chat/route.ts:4 (or delete the route) and redeploy.
2. OpenAI API: Cap the affected project: Project settings > Limits > Spend > Edit spend limit, enter a low Monthly spend limit, turn on Enforce a hard limit, Save.
3. OpenAI API: Cap the whole organization: Organization limits (https://platform.openai.com/settings/organization/limits) > Spend > Edit spend limit, turn on Enforce a hard limit, Save.
4. OpenAI API: Lower the project's per-model rate limits on the same project Limits page so a runaway loop is throttled even before the cap trips.
5. OpenAI API: If a key may be leaked, rotate it now on the API keys page (https://platform.openai.com/settings/organization/api-keys): create a replacement, deploy it, then revoke the old key.

- OpenAI API hard cap: Yes: a monthly hard spend limit on the organization or a project makes affected requests fail with 429 (organization_spend_limit_exceeded or project_spend_limit_exceeded), but enforcement is not instantaneous so recorded spend can slightly exceed it; spend alerts alone never stop traffic.
- Provider steps come from references/caps.json; caps data checked 2026-10-13 (1 day ago). Dashboards move; if a menu is not where the step says, search the provider's docs for the same setting.

## What spiked

- Provider: OpenAI.
- Source: OpenAI usage export, 7 rows, 2026-10-06 to 2026-10-12.
- Daily spend went from about $1.14 (typical day) to $1,243.2 at 2026-10-12: 1091x, a sudden jump starting 2026-10-10.
- Driver by model: gpt-4o (100% of the increase).
- Driver by api key: sk-proj-...q7Xa (100% of the increase).
- Driver by project: recipe-helper (100% of the increase).
- Per request: output tokens 240 to 2,400 (10.0x), input tokens 150 to 1,500 (10.0x); requests 78.4x.
- Shape: sudden, more requests.
- Abuse check: nothing in the data points at an unknown key or project. Still check usage by key and IP on the provider's usage page if the cause below does not explain the whole spike.
…
## Most likely causes in the code

### 1. Public endpoint calls OpenAI with no sign-in or rate limit [high]

- **Finding:** app/api/chat/route.ts:4 (POST /api/chat) calls an LLM (OpenAI gpt-4o at app/api/chat/route.ts:7) and anyone on the internet can call it: there is no authentication check and no rate limit.
- **Evidence:** app/api/chat/route.ts:4  export async function POST(req: NextRequest) {; app/api/chat/route.ts:7  const completion = await openai.chat.completions.create({
- **Impact:** Anyone who finds the URL (bots scan for /api/chat and similar within days) can use your paid API as a free proxy. You pay for every call. One user, one script or one leaked session can call it as fast as the network allows. There is no point where the code says no.
…
Other cost risks in this repo that do not match this spike (fix after): No output limit on gpt-4o call (app/api/chat/route.ts:7, OpenAI); Whole conversation history sent on every call (app/api/chat/route.ts:7, OpenAI).

## Cap checklist (so it cannot happen again)

Caps and alerts only; the full checklist (rate limits, key restrictions, usage pages) is in scan.py output and in --json.

### OpenAI API

_Source: references/caps.json, checked 2026-10-13._

- Hard cap: Yes: a monthly hard spend limit on the organization or a project makes affected requests fail with 429 (organization_spend_limit_exceeded or project_spend_limit_exceeded), but enforcement is not instantaneous so recorded spend can slightly exceed it; spend alerts alone never stop traffic.
- **Caps:**
  - Organization hard spend limit: applies to all projects; returns 429 organization_spend_limit_exceeded.
  - Project hard spend limit: applies only to traffic billed to that project; returns 429 project_spend_limit_exceeded.
  - Both reset with the next monthly cycle; raising or removing the reached limit resumes traffic after it propagates.
…
- **Budgets and alerts:**
  - Spend alerts on the Limits page (organization or project) send an email at a threshold; API traffic continues.
…
Fix offered (applied only after you approve)
 import { NextRequest, NextResponse } from "next/server";
 import { openai } from "@/lib/openai";
+import { Ratelimit } from "@upstash/ratelimit";
+import { Redis } from "@upstash/redis";
+
+const ratelimit = new Ratelimit({
+  redis: Redis.fromEnv(),
+  limiter: Ratelimit.slidingWindow(10, "1 h"),
+  prefix: "chat",
+});

 export async function POST(req: NextRequest) {
+  const ip = req.headers.get("x-forwarded-for")?.split(",")[0] ?? "anon";
+  const { success } = await ratelimit.limit(ip);
+  if (!success) {
+    return NextResponse.json({ error: "Too many requests" }, { status: 429 });
+  }
+
   const { messages } = await req.json();

   const completion = await openai.chat.completions.create({
     model: "gpt-4o",
+    max_tokens: 500,
     messages: [
       { role: "system", content: "You are a friendly recipe helper." },
-      ...messages,
+      ...messages.slice(-10),
     ],
   });

This is the "must stay public" variant from the script's fix list. If the app can require sign-in, Claude offers that instead. A per-IP limit still allows steady spend from many IPs, so the OpenAI project hard limit stays on as the backstop.

2. Before launch: an SMS code endpoint open to pumping

An Express sign-up service sends a one-time code by Twilio to whatever number is posted. There is a global per-IP rate limit, which looks like protection.

src/routes/otp.js
otp.post("/send-code", async (req, res) => {
  const { phone } = req.body;
  const code = newCode(phone);
  await client.messages.create({ to: phone, from: FROM, body: `Your code is ${code}` });
  res.json({ ok: true });
});
src/server.js
const app = express();
app.use(express.json());
app.use(rateLimit({ windowMs: 60_000, limit: 30 }));
app.use("/api/auth", otp);
Developer
We open sign-ups on Friday. Check this repo for cost risks.
Script output (trimmed)
1 uncapped cost risk found (1 high). Worst: SMS sends with no per-user or per-number limit (src/routes/otp.js:7).

Top fixes:
1. SMS sends with no per-user or per-number limit (src/routes/otp.js:7): Rate limit per phone number or address and per IP (for example 3 per hour), and add a bot check (Turnstile, reCAPTCHA) before sending.

Scanned 7 files (4 code files, 2 entry points, 1 paid call sites, 0 scheduled jobs) in 0.0s.

## Uncapped cost risks (1), worst first

### 1. SMS sends with no per-user or per-number limit [high]

- **Finding:** src/routes/otp.js:7 (POST /send-code) sends an SMS to a number or address taken from the request, with no per-number limit, captcha or per-recipient cap (it has a per-IP rate limit (rateLimit), which pumping bots defeat by rotating through many IPs; allow only the countries you serve and cap sends per number). Bots use endpoints like this to send thousands of messages to premium-rate numbers (SMS pumping) or to spam addresses, billed to you.
- **Evidence:** src/routes/otp.js:7  otp.post("/send-code", async (req, res) => {; src/routes/otp.js:10  await client.messages.create({ to: phone, from: FROM, body: `Your code is ${code}` });
- **Impact:** SMS pumping: bots submit thousands of premium-rate or international numbers to OTP and invite forms, and you pay per message (the fraudsters get a revenue share from the carrier). Email endpoints get abused to spam, which costs money and your sender reputation.
- **Fix:**
  1. Rate limit per phone number or address and per IP (for example 3 per hour), and add a bot check (Turnstile, reCAPTCHA) before sending.
  2. Allow only the countries you serve (Twilio Messaging Geo Permissions and Verify Geo Permissions), and turn on Twilio Verify Fraud Guard if you use Verify.
  3. Never send to a number or address the user has not confirmed for anything other than verification.
- **Cost:** Estimate: US SMS costs $0.011 to $0.013 per segment including carrier fees (Twilio US pricing, https://www.twilio.com/en-us/sms/pricing/us, checked 2026-10-13), so 10,000 pumped messages to US numbers is about $108 to $133. Pumping usually targets international premium numbers, which cost much more per message.
- Causes: 21. Provider: Twilio. Blast radius score 30.0.
…
## Spend caps for the providers in this code

_Spend-cap data checked 2026-10-13 (1 day ago)._

### Twilio (Programmable Messaging, Verify)

_Source: references/caps.json, checked 2026-10-13._

- Hard cap: No dollar cap: Twilio offers fraud blocking (SMS Pumping Protection, Verify Fraud Guard), Geo Permissions and usage-trigger webhooks; a balance that hits zero can suspend a prepaid project, so disabling auto-recharge is the closest thing to a cap, and a usage trigger can call your code to suspend a subaccount.
- **Caps:**
  - Geo Permissions limit which countries can receive SMS; new accounts allow only the home country.
…
Fix offered (applied only after you approve)
 import { Router } from "express";
 import { client, FROM } from "../lib/twilio.js";
 import { newCode, checkCode } from "../lib/codes.js";
+import { Ratelimit } from "@upstash/ratelimit";
+import { Redis } from "@upstash/redis";
+
+// 3 codes per number per hour; only the country you serve (US here)
+const perNumber = new Ratelimit({
+  redis: Redis.fromEnv(),
+  limiter: Ratelimit.fixedWindow(3, "1 h"),
+  prefix: "otp-number",
+});
+const ALLOWED_NUMBER = /^\+1\d{10}$/;

 export const otp = Router();

 otp.post("/send-code", async (req, res) => {
   const { phone } = req.body;
+  if (typeof phone !== "string" || !ALLOWED_NUMBER.test(phone)) {
+    return res.status(400).json({ error: "Unsupported number" });
+  }
+  const { success } = await perNumber.limit(phone);
+  if (!success) {
+    return res.status(429).json({ error: "Too many codes for this number" });
+  }
   const code = newCode(phone);
   await client.messages.create({ to: phone, from: FROM, body: `Your code is ${code}` });

Claude also lists the dashboard steps the code cannot do: untick unused countries in Twilio Geo Permissions and turn on SMS Pumping Protection. A bot check (Turnstile or reCAPTCHA) before sending is the other half of the script's first fix.

3. Correctly not alarming: a signed-in chat with a per-user limit

A study helper where only signed-in users can ask questions, each user gets 20 questions an hour, and the question is capped at 2,000 characters. The call has no max_tokens.

app/api/chat/route.ts
  const session = await getSession();
  if (!session?.user?.email) {
    return NextResponse.json({ error: "Sign in first" }, { status: 401 });
  }

  const { success } = await ratelimit.limit(session.user.email);
  if (!success) {
    return NextResponse.json({ error: "Too many requests" }, { status: 429 });
  }

  const { question } = await req.json();
  if (typeof question !== "string" || question.length > 2000) {
    return NextResponse.json({ error: "Question too long" }, { status: 400 });
  }

  const completion = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [
      { role: "system", content: "Explain the answer step by step for a high school student." },
      { role: "user", content: question },
    ],
  });
Developer
Can you check this app for anything that could blow up our OpenAI bill?
Script output (trimmed)
No uncapped spend confirmed in the code that was scanned; 1 thing to check.

Scanned 7 files (4 code files, 1 entry points, 1 paid call sites, 0 scheduled jobs) in 0.0s.

## Check these (1)

Possible cost risks the scan could not confirm from the code alone. Open each one before acting on it.

- [1] No output limit on gpt-4o-mini call: app/api/chat/route.ts:22 calls gpt-4o-mini with no output limit (no max_tokens or equivalent), so each reply can run to the model's maximum. Reached by any signed-in user via POST /api/chat (app/api/chat/route.ts:6). The scan found no open endpoint, loop or unbounded caller input that reaches it, so one reply's worst case is the whole risk. Still set a cap.
…
## What it can't see

- Real traffic, usage or invoices (paste a usage export into triage.py for that).
- Provider dashboards: whether spending caps, budgets, alerts, rate limits or key restrictions are already set.
- Whether a key is being used by someone else right now (check usage by key, project and IP on the provider's usage page).
- Settings that live only in host dashboards (Vercel, Netlify, Cloudflare, Firebase console) or in infrastructure outside this folder.
- Price changes, model changes, or free tiers and credits ending.
Fix offered (applied only after you approve)
   const completion = await openai.chat.completions.create({
     model: "gpt-4o-mini",
+    max_tokens: 800,
     messages: [
       { role: "system", content: "Explain the answer step by step for a high school student." },
       { role: "user", content: question },
     ],
   });

Results

Two test rounds of bill-spike emergencies, Bill Spiked compared with plain Claude:

TestBill SpikedPlain Claude
7 blind bill-spike emergencies (Oct 13): fully solved6 of 71 of 7
Same 7: wrong claims about provider spending caps04
4 new held-out emergencies (Oct 14): fully solved3 of 42 of 4
Same 4: wrong claims about provider spending caps00

The cap data behind those answers is references/caps.json: 23 providers (OpenAI, Anthropic, Gemini API, AWS, Google Cloud, Firebase, Azure OpenAI, Vercel, Netlify, Cloudflare, Supabase, Twilio, SendGrid, Resend, Replicate, MongoDB Atlas, Render, Fly.io, Groq, Mistral, OpenRouter, xAI and ElevenLabs). Every entry lists its sources, and each source has the date it was checked.

Install Bill Spiked

Download bill-spiked.zip Free during launch. Checksums

You need Python 3.8 or newer where your agent runs its commands, because the checks are small Python scripts (standard library only, nothing to pip install). macOS and Linux usually have python3 already; on Windows install it from python.org.
Claude Code
  1. Download bill-spiked.zip (button above).
  2. Unzip it into your personal skills folder so it works in every project:
    unzip ~/Downloads/bill-spiked.zip -d ~/.claude/skills/
    Or into one project only (commit it to share it with your team):
    unzip ~/Downloads/bill-spiked.zip -d .claude/skills/
  3. Check it landed:
    ls ~/.claude/skills/bill-spiked/SKILL.md
  4. Use it. Claude Code picks up new skills without a restart (if ~/.claude/skills did not exist when your session started, run /reload-skills). Ask in plain words, for example “my OpenAI bill spiked overnight” or paste the usage export or billing alert, or type /bill-spiked.
Claude.ai (web and desktop app)
  1. Open Settings > Capabilities and make sure Code execution and file creation is on. Skills need it. (Available on Pro, Max, Team and Enterprise plans; on Enterprise an Owner enables Skills first.)
  2. In the Skills section, click Upload skill and choose bill-spiked.zip. Do not unzip it.
  3. Check that it appears in your Skills list and switch it on.
  4. Start a chat, attach your project as a zip (or paste the error or alert) and ask, for example “my OpenAI bill spiked overnight”. In Claude.ai the scripts run in Claude’s sandbox on the files you upload, not on your computer.
Cursor
  1. Unzip into Cursor’s skills folder for all projects:
    unzip ~/Downloads/bill-spiked.zip -d ~/.cursor/skills/
    or into a project: .cursor/skills/. Cursor also reads ~/.claude/skills/, so a Claude Code install works in Cursor too.
  2. In Agent chat, type / and pick bill-spiked, or just ask: “my OpenAI bill spiked overnight”.
OpenAI Codex
  1. Unzip into your user skills folder:
    unzip ~/Downloads/bill-spiked.zip -d ~/.agents/skills/
    or into .agents/skills/ at the root of a repo.
  2. Mention it with $bill-spiked (or pick it from /skills), or just ask: “my OpenAI bill spiked overnight”.
Other agents, updates and removal
  • Any agent that reads SKILL.md files: unzip into its skills folder. Each skill is a folder with SKILL.md, scripts/ and references/.
  • Update: download the new zip and unzip it over the old one with unzip -o.
  • Remove: delete bill-spiked/ from the skills folder.
  • Check the download: shasum -a 256 bill-spiked.zip and compare with SHA256SUMS.txt.

FAQ

Does it change my code?

Not on its own. The scripts are read-only. Claude shows each fix as a diff and applies it only after you approve. The one optional write is a line of counts (never keys) to .techfix/ledger.md, with your OK.

Does my code or usage data leave my machine?

The scripts run locally with Python's standard library and make no network calls. Claude reads your files and what you paste the same way it does in any session. Keys are masked in every report, including echoes of what you pasted.

Which stacks does it cover?

JavaScript, TypeScript and Python code, plus AWS SAM, serverless.yml, Terraform, CDK, Step Functions, Vercel, Netlify, Firebase, Wrangler, Cloud Run, App Engine and Fly configs. It knows the OpenAI, Anthropic, Gemini, Bedrock, Replicate, Twilio, SendGrid, Resend, Firestore and DynamoDB SDKs and the common agent frameworks. Go, Ruby, PHP, Java, Pulumi and Kubernetes are not analysed, and the report says so.

How long does it take?

The scripts take 0.2 to 0.3 seconds on these examples (triage.py 0.3 s, scan.py 0.2 s each). Claude's review adds time, since it opens each finding and walks the checklist.

How is this different from asking my agent directly?

Two things. First, order: it gives you the stop steps before any diagnosis. Second, the cap facts come from a dated, sourced file instead of memory, and the skill tells Claude to repeat them as written. That is where plain Claude went wrong in our tests: it made 4 wrong claims about provider spending caps on 7 emergencies, the skill made 0.

What does it cost?

Bill Spiked is normally $7. It is free during launch.