Write abuse cases by hand for any function that should run once or a limited number of times: coupons, trials, transfers, exports, invitations. Call it again, call it out of order, and call it in parallel. Fix failures with a hard check in the backend or database, not in the client.
Why a scanner will not find these
A scanner knows the shape of requests. It does not know that a discount may be used once, or that a user may download ten documents a month. The OWASP Web Security Testing Guide has a test for this called testing the number of times a function can be used. It asks whether the application is smart enough to stop users from exceeding logical limits, and its objective is to find functions that must limit how often they run and to assess whether the limit is validated.
OWASP's 2023 API list covers the wider theme under unrestricted access to sensitive business flows (API6:2023).
A method in four steps
- Find the limited functions. Read project documentation, ask product owners, and explore the workflow. Look for anything described as "once", "per month", "first order", "up to", or "one per account".
- Write an abuse case for each. State the rule and the attempt: "A coupon applies once. Try to apply it a second time."
- Attack three ways. Repeat the call sequentially, repeat it out of order, and send it concurrently.
- Check the backend, not just the answer. Look at the data afterward. A response that says "already used" while a record was created anyway is still a flaw.
The guide's own examples are web workflow ones: returning to a discount page after applying a one-time discount, or loading and unloading a cart to stack discounts. APIs have equivalents: replaying the same request, replaying an older request with a still-valid token, or batching.
Example abuse cases
| Rule | Abuse case |
|---|---|
| One coupon per order | Apply the coupon, remove an item, apply again |
| Free trial once per account | Create a second account with a variation of the email |
| Ten exports per month | Send eleven requests in parallel |
| Transfer limit per day | Split a transfer into many small calls through a batch endpoint |
| Invitation links expire | Use an expired link with a fresh session |
| Password reset code valid once | Use the same code twice |
Fixing it correctly
The guide recommends hard controls enforced on the backend or database, such as invalidating a coupon at the database level or keeping a per-user counter tied to the session. Two practical consequences:
- Use atomic operations or unique constraints, so two parallel requests cannot both succeed.
- Never trust a counter or flag that the client sends.
OWASP also advises throttling sensitive operations such as one-time password validation and password recovery, and setting spending limits or billing alerts for third-party calls your API triggers.
Turn each case into a regression test
Once an abuse case fails and gets fixed, keep it as an automated test. These are cheap to automate because the expected result is a single yes or no: the second use is refused and no extra record exists.
Who writes them
A tester and a product owner together are better than either alone. The product owner knows the rule, and the tester knows how to bend it.
Worked example: a ride credit system
A ride sharing API gives each new user a one-time welcome credit and lets users refer friends for a bonus. Credits are spent at checkout. A tester works with the product owner for an hour and lists the rules.
| Rule | Where enforced |
|---|---|
| Welcome credit once per new account | Account creation |
| Referral bonus once per referred friend | Referral claim route |
| Credit cannot exceed the ride price | Checkout |
| Promo code usable once per user | Promo route |
| Cancelling a ride returns the credit once | Cancellation route |
Abuse cases written from the rules.
- Create an account, claim the welcome credit, then call the claim route again with the same token. Expected: refused, credit unchanged.
- Claim a referral twice using two parallel requests. Expected: one succeeds.
- Use a promo code on a ride, cancel the ride, and use the code again. Expected: depends on the rule. The product owner says a cancelled ride should restore the code once, no more.
- Cancel a ride twice quickly in parallel. Expected: credit is restored once.
- Apply a credit larger than the ride price by editing the amount field in the request. Expected: the server caps it.
What happens. Case 1 passes. Case 2 fails: two parallel referral claims both succeed, because the route checks "already claimed" and then writes, with no lock between. Case 3 passes. Case 4 fails: the cancellation route restores credit each time it runs before the ride's status is updated, so two near-simultaneous cancels give double credit. Case 5 fails: the server trusts the amount the client sends.
Three of five cases fail. A scanner would have found none, because every request in these cases is individually valid.
The fixes
- For the parallel cases, use an atomic update or a unique constraint, so the second request cannot succeed. For referrals, a unique index on the referrer and referred pair does it. For cancellation, update the ride status in the same transaction that restores credit and reject if it was already cancelled.
- For the amount, compute the credit server side from price and balance, and ignore any client amount.
- Add an audit log line for each credit change, so odd patterns show up in monitoring.
Turning the cases into tests
Each abuse case becomes an automated test with a clear expected result. Parallel cases use a small number of concurrent requests, enough to trigger the race. They run in CI against a test database, and they are quick.
Prioritizing
Start with rules tied to money, access or personal data. A rule about a cosmetic badge can wait. The product owner's view of what hurts the business is the best ranking you have.
A repeatable habit
When a new feature has a limit or a one-time rule, add the abuse case to the definition of done. Writing the rule down and the way to break it in the same ticket costs minutes and prevents most of the surprises.
Try it yourself
Open the demo and notice how every example finding comes from a request pattern a tool can recognize. Then ask which of the ride credit failures above would ever show up in a list like this.
Click through it at your own pace, or open it full screen.
Quick check
Is a rate limit the same as a usage limit?
No. A rate limit controls speed. A usage limit controls how many times something may happen at all, however slowly.
Why test concurrency?
Two requests sent at the same moment can both pass a check that happens before the record is updated. Atomic updates or unique constraints close that gap.
Can an automated tool find any logic flaws?
Some, where a rule can be expressed as a pattern, such as a missing limit on a login route. Rules that depend on what your business means, like one credit per friend, need a human to write the case.
Sources
- OWASP WSTG: Number of times a function can be used limits
- OWASP API Security Top 10 2023
- OWASP API4:2023 Unrestricted Resource Consumption
Last reviewed: 19 October 2026.