{"course":{"slug":"building-with-ai","title":"Building with AI","url":"https://addaly.com/learn/building-with-ai","level":"intermediate","track":"build"},"lesson":{"slug":"denial-of-wallet","title":"Denial of wallet: attacks on your bill","url":"https://addaly.com/learn/building-with-ai/denial-of-wallet","position":67,"of":80,"minutes":8,"answer":"A public endpoint that calls a paid model can be made to spend a thousand dollars an hour by one script, so the defences (authentication before the model, per-user token budgets claimed before the call.","keyIdea":"A public endpoint that calls a paid model can be made to spend a thousand dollars an hour by one script, so the defences (authentication before the model, per-user token budgets claimed before the call, caps on input and output, a spend alert and a kill switch) are part of the feature, not an operational afterthought."},"body":{"format":"markdown","text":"## The arithmetic of one script\n\nAn endpoint with no authentication that forwards text to a frontier model. An attacker's script sends ten requests a second, each with a 4,000-token prompt and asking for a 1,000-token reply. Input: 40,000 tokens a second, around $0.12 at $3 per million, which is about $430 an hour. Output: 10,000 tokens a second, around $0.15 at $15 per million, about $540 an hour. Call it a thousand dollars an hour, twenty-four thousand a day, from one machine with no skill required, and the provider will bill you for every token because every request was valid.\n\nThis is not hypothetical. Free-tier \"try it\" boxes on product pages have been discovered by people running scripts within days of launch, and the bill arrives before the discovery.\n\n> Figure: One unauthenticated endpoint, one script, one hour. Bars (dollars an hour): Input tokens 430; Output tokens 540; Total, from one machine 970. Ten requests a second, 4,000 tokens in and 1,000 out, at $3 and $15 per million. Every request is valid, so the provider bills all of it. A per-user budget claimed before the call, a bot check at the edge and a kill switch are part of the feature, not operations.\n\n## Where the bill is exposed\n\n- An endpoint reachable without a login.\n- A login with no per-user budget, so one account with a script is enough.\n- A generous `max_tokens` on every request.\n- No cap on input length, so a single request can carry a hundred thousand tokens of pasted text.\n- An agent loop without the three budgets from the loop lesson: the commonest \"attacker\" is your own misconfigured loop.\n- A retry policy without backoff, which the first module showed will attack your own bill during an outage.\n- A free trial generous enough to be worth signing up for a thousand times.\n\n## The defences, in layers\n\n**Authentication before the model.** No anonymous request reaches a paid call. Where a feature must be open to visitors, put a bot check at the edge (Cloudflare Turnstile and hCaptcha are free) and issue a short-lived token before the first model call. That is exactly how this site gates guest posting.\n\n**A per-user budget, claimed before the call.** Each user has a daily allowance in tokens or in requests. Before calling the model, increment a counter with an expiry (a Redis `INCR` with a TTL, or a per-user object in a serverless store) and refuse if it is over. Claim before, not after: two hundred requests fired in parallel all pass an \"after\" check because none has been counted yet, and all two hundred run. Refund the claim if the provider call fails, so an outage does not eat a user's allowance. This site's own AI feature does precisely this: quota claimed last, refunded on provider failure.\n\n**Rate limits by IP and globally.** Token buckets per address catch one machine; a global ceiling on requests per minute catches a distributed one. Fail closed on the global limit: better a few minutes of refusals than a night of spend.\n\n**Caps on both ends.** A maximum input length in characters, enforced before tokenisation, and a `max_tokens` per tier. A guest gets a small model and a short reply.\n\n**A spend alert and a kill switch.** Set a daily budget at the provider with an alert, and add an environment flag that disables the feature in seconds without a deployment. A kill switch that requires a deploy is not a kill switch at three in the morning.\n\n**Watch the distribution.** From the traces, compute cost per user per day. If the top ten users are a third of the spend, look at who they are. Alert when a new account enters the top one per cent within its first hour; that is the signature of a script.\n\n## Tiers are budgets\n\nMap allowances to the identity ladder: a visitor gets a handful of calls on the cheapest model, a member gets more, a verified account more again, a paying customer is metered against what they pay. The cheaper model for lower tiers is not only about cost; it lowers the reward for abusing the tier.\n\n## Caching is also a defence\n\nThe repeat attack (the same prompt ten thousand times) is served from a response cache at no cost once the first response is stored, and the later module on caching covers the mechanics. A cache keyed on the exact prompt turns the simplest attack into a free one for you.\n\n## The trial trap\n\nA free trial with a month of generous limits attracts sign-ups from scripts, each with a disposable email. Either make the trial small enough not to be worth the effort, or require something scarce (a phone number, a card that is not charged) before the paid model is reachable. Weigh that against the friction it adds for real people; the answer depends on how much a trial can cost you, which the arithmetic at the top of this lesson tells you."},"check":{"question":"A per-user daily budget is enforced by counting a request after the model call returns. A user fires 300 requests at once and all of them run. Why?","options":[{"text":"The counter's expiry reset at midnight during the burst, clearing the count.","correct":false,"tempting":"A timing coincidence that would let through at most one more day's allowance, not an unbounded burst."},{"text":"The provider processed the requests in parallel and the counter could not keep up with the rate at which the responses came back to the server.","correct":false,"tempting":"Counters handle thousands of increments a second; speed is not the issue, order of operations is."},{"text":"None of the 300 had been counted when each was checked, because counting happens after the call; the claim must happen before.","correct":true},{"text":"Parallel requests from one user are treated as separate sessions with separate budgets.","correct":false,"tempting":"Budgets are keyed by user, not by session; the flaw is when the count is taken, not how it is keyed."}]},"prev":{"slug":"what-you-send-to-the-provider","title":"What you send to the provider, and what you can actually verify","url":"https://addaly.com/learn/building-with-ai/what-you-send-to-the-provider"},"next":{"slug":"reducing-hallucination-by-mechanism","title":"Reducing hallucination by mechanism, not by instruction","url":"https://addaly.com/learn/building-with-ai/reducing-hallucination-by-mechanism"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Denial of wallet: attacks on your bill', in Building with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-with-ai/denial-of-wallet (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Denial of wallet: attacks on your bill","courseTitle":"Building with AI","module":{"index":7,"slug":"safety-security-and-the-adversary","title":"Safety, security and the adversary"},"answer":null,"keyIdea":"A public endpoint that calls a paid model can be made to spend a thousand dollars an hour by one script, so the defences (authentication before the model, per-user token budgets claimed before the call, caps on input and output, a spend alert and a kill switch) are part of the feature, not an operational afterthought.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"type":"bars","unit":"dollars an hour","title":"One unauthenticated endpoint, one script, one hour","series":[{"label":"Input tokens","value":430},{"label":"Output tokens","value":540},{"label":"Total, from one machine","value":970,"highlight":true}],"caption":"Ten requests a second, 4,000 tokens in and 1,000 out, at $3 and $15 per million. Every request is valid, so the provider bills all of it. A per-user budget claimed before the call, a bot check at the edge and a kill switch are part of the feature, not operations."},"alt":"Figure: One unauthenticated endpoint, one script, one hour. Bars (dollars an hour): Input tokens 430; Output tokens 540; Total, from one machine 970. Ten requests a second, 4,000 tokens in and 1,000 out, at $3 and $15 per million. Every request is valid, so the provider bills all of it. A per-user budget claimed before the call, a bot check at the edge and a kill switch are part of the feature, not operations."}},"blocks":[{"t":"h2","id":"the-arithmetic-of-one-script","runs":[{"text":"The arithmetic of one script"}]},{"t":"p","runs":[{"text":"An endpoint with no authentication that forwards text to a frontier model. An attacker’s script sends ten requests a second, each with a 4,000-token prompt and asking for a 1,000-token reply. Input: 40,000 tokens a second, around $0.12 at $3 per million, which is about $430 an hour. Output: 10,000 tokens a second, around $0.15 at $15 per million, about $540 an hour. Call it a thousand dollars an hour, twenty-four thousand a day, from one machine with no skill required, and the provider will bill you for every token because every request was valid."}]},{"t":"p","runs":[{"text":"This is not hypothetical. Free-tier “try it” boxes on product pages have been discovered by people running scripts within days of launch, and the bill arrives before the discovery."}]},{"t":"h2","id":"where-the-bill-is-exposed","runs":[{"text":"Where the bill is exposed"}]},{"t":"ul","items":[[{"text":"An endpoint reachable without a login."}],[{"text":"A login with no per-user budget, so one account with a script is enough."}],[{"text":"A generous "},{"text":"max_tokens","code":true},{"text":" on every request."}],[{"text":"No cap on input length, so a single request can carry a hundred thousand tokens of pasted text."}],[{"text":"An agent loop without the three budgets from the loop lesson: the commonest “attacker” is your own misconfigured loop."}],[{"text":"A retry policy without backoff, which the first module showed will attack your own bill during an outage."}],[{"text":"A free trial generous enough to be worth signing up for a thousand times."}]]},{"t":"h2","id":"the-defences-in-layers","runs":[{"text":"The defences, in layers"}]},{"t":"p","runs":[{"text":"Authentication before the model.","b":true},{"text":" No anonymous request reaches a paid call. Where a feature must be open to visitors, put a bot check at the edge (Cloudflare Turnstile and hCaptcha are free) and issue a short-lived token before the first model call. That is exactly how this site gates guest posting."}]},{"t":"p","runs":[{"text":"A per-user budget, claimed before the call.","b":true},{"text":" Each user has a daily allowance in tokens or in requests. Before calling the model, increment a counter with an expiry (a Redis "},{"text":"INCR","code":true},{"text":" with a TTL, or a per-user object in a serverless store) and refuse if it is over. Claim before, not after: two hundred requests fired in parallel all pass an “after” check because none has been counted yet, and all two hundred run. Refund the claim if the provider call fails, so an outage does not eat a user’s allowance. This site’s own AI feature does precisely this: quota claimed last, refunded on provider failure."}]},{"t":"p","runs":[{"text":"Rate limits by IP and globally.","b":true},{"text":" Token buckets per address catch one machine; a global ceiling on requests per minute catches a distributed one. Fail closed on the global limit: better a few minutes of refusals than a night of spend."}]},{"t":"p","runs":[{"text":"Caps on both ends.","b":true},{"text":" A maximum input length in characters, enforced before tokenisation, and a "},{"text":"max_tokens","code":true},{"text":" per tier. A guest gets a small model and a short reply."}]},{"t":"p","runs":[{"text":"A spend alert and a kill switch.","b":true},{"text":" Set a daily budget at the provider with an alert, and add an environment flag that disables the feature in seconds without a deployment. A kill switch that requires a deploy is not a kill switch at three in the morning."}]},{"t":"p","runs":[{"text":"Watch the distribution.","b":true},{"text":" From the traces, compute cost per user per day. If the top ten users are a third of the spend, look at who they are. Alert when a new account enters the top one per cent within its first hour; that is the signature of a script."}]},{"t":"h2","id":"tiers-are-budgets","runs":[{"text":"Tiers are budgets"}]},{"t":"p","runs":[{"text":"Map allowances to the identity ladder: a visitor gets a handful of calls on the cheapest model, a member gets more, a verified account more again, a paying customer is metered against what they pay. The cheaper model for lower tiers is not only about cost; it lowers the reward for abusing the tier."}]},{"t":"h2","id":"caching-is-also-a-defence","runs":[{"text":"Caching is also a defence"}]},{"t":"p","runs":[{"text":"The repeat attack (the same prompt ten thousand times) is served from a response cache at no cost once the first response is stored, and the later module on caching covers the mechanics. A cache keyed on the exact prompt turns the simplest attack into a free one for you."}]},{"t":"h2","id":"the-trial-trap","runs":[{"text":"The trial trap"}]},{"t":"p","runs":[{"text":"A free trial with a month of generous limits attracts sign-ups from scripts, each with a disposable email. Either make the trial small enough not to be worth the effort, or require something scarce (a phone number, a card that is not charged) before the paid model is reachable. Weigh that against the friction it adds for real people; the answer depends on how much a trial can cost you, which the arithmetic at the top of this lesson tells you."}]}],"check":{"question":[{"text":"A per-user daily budget is enforced by counting a request after the model call returns. A user fires 300 requests at once and all of them run. Why?"}],"options":[{"text":[{"text":"The counter’s expiry reset at midnight during the burst, clearing the count."}],"correct":false,"tempting":[{"text":"A timing coincidence that would let through at most one more day’s allowance, not an unbounded burst."}]},{"text":[{"text":"The provider processed the requests in parallel and the counter could not keep up with the rate at which the responses came back to the server."}],"correct":false,"tempting":[{"text":"Counters handle thousands of increments a second; speed is not the issue, order of operations is."}]},{"text":[{"text":"None of the 300 had been counted when each was checked, because counting happens after the call; the claim must happen before."}],"correct":true,"tempting":null},{"text":[{"text":"Parallel requests from one user are treated as separate sessions with separate budgets."}],"correct":false,"tempting":[{"text":"Budgets are keyed by user, not by session; the flaw is when the count is taken, not how it is keyed."}]}],"order":[0,1,3,2],"why":null},"prev":{"slug":"what-you-send-to-the-provider","title":"What you send to the provider, and what you can actually verify","minutes":9},"next":{"slug":"reducing-hallucination-by-mechanism","title":"Reducing hallucination by mechanism, not by instruction","minutes":9}}}