# Stop abuse of your app's AI feature: per-user limits and a budget alarm

- Author: RomanMotion (https://wurk.fun/user/RomanMotion)
- Published: 2026-11-08
- Canonical (HTML): https://wurk.fun/blog/stop-abuse-of-your-app-s-ai-feature-per-user-limits-and-a-budget-alarm
- Cover image: https://ik.imagekit.io/wurk/browser-uploads/f1c626b5c17e7f0a61dfbbe7a20d85ae7b33a20527243aeec2ee1d0a8b87562a/27697b47-3d5b-4b38-8187-635304fa983b/cover-abuse.jpg?wurkUpload=v1.02Y3h3OGbo0EN3i6i-Fnf36SXEZFCnUSprP-tKchK-g

---

You built an app with an AI button, and each tap sends a request to a paid API. A loop, a bot, or a user sending huge inputs can drain your quota or run up a bill before you notice. This guide gives you a set of server-side controls to add and a way to test them before launch.

Require an identity at the endpoint, limit request frequency and size, and track a daily quota for each user. Add a separate app-wide budget guard and an alert, then check what spending controls your provider actually offers. Keep the API key and enforcement on the server: a disabled button in the app is not a spending limit.

## Start at the endpoint

Imagine your app summarizes text. The screen sends that text to your server, and your server calls the paid API. If the endpoint accepts unlimited requests, a script can repeatedly call it without using the screen at all. OWASP lists [unbounded consumption](https://genai.owasp.org/llmrisk/llm102025-unbounded-consumption/) as a risk for AI applications and recommends limits and quotas.

Require sign-in or a separately issued client key before allowing the request through. Verify the session or key on the server, then use the authenticated user or key owner as the identity for limits and logs. Don't use a shared key embedded in a mobile app as proof of an individual user's identity.

Keep the paid provider's secret key on the server, out of browser code, app packages, screenshots, and logs. Give each client key its own identity and a way to revoke it. Protect key issuance too: otherwise, someone who hits a limit could simply create more keys.

Test the endpoint directly without credentials. It should reject the call before contacting the paid API.

A coding agent that can run commands can do this check for you with one request from the terminal and show you the response. If you need a harness for that, you can [try the zcode agent harness free](https://zcode.z.ai/invite/ALWM9JUA).

## Slow bursts with rate limits

Set a server-side rate limit for each authenticated user or client key. Add a separate IP-based limit as a secondary control, including for rejected sign-in attempts. Users can share an IP, and an attacker may use more than one, so an IP is not a reliable substitute for an account identity.

When a caller exceeds the rate limit, respond with HTTP **429 Too Many Requests** and a `Retry-After` header. The header can specify a delay in seconds or an HTTP date; make it match the actual time your limit resets. [RFC 6585](https://www.rfc-editor.org/rfc/rfc6585) defines the status and allows the header, and [MDN's 429 page](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/429) explains both.

Show a useful message in the app, such as "Too many requests. Please try again later." Make the client respect `Retry-After` instead of immediately retrying in a loop.

If your app runs on multiple server instances, store rate-limit state in shared storage. Otherwise, each instance may enforce its own separate allowance.

## Cap request size and daily use

A rate limit controls frequency, not the size of each request. On the server, reject inputs above a size you choose for the feature. Also set a maximum output-token limit on the provider request, using the option supported by that provider.

Explain the input limit in the interface, but enforce it again at the endpoint. If the feature accepts uploaded files or conversation history, include their contents in your size checks rather than counting only the latest message.

Next, set a daily request quota per authenticated user. Define the reset timezone and make the counter update atomic in shared storage, so concurrent requests cannot all pass the same remaining allowance. Decide whether failed requests count; counting attempts is a simple conservative starting point.

The counter sketch in the prompt section below shows the shape. `atomic_increment_if_below` stands for a storage operation that checks and increments as one action, not an ordinary read followed by a separate write.

A request count alone does not cap cost: calls can use different amounts of input and output. Pair it with the size limits and the app-wide budget guard.

## Add a budget guard and alarm

Put a daily budget and an AI-feature kill switch in server configuration. The switch should stop new paid calls while leaving the rest of the app usable where possible. Show users a clear "temporarily unavailable" message rather than silently failing.

Before sending a request, reserve a conservative estimated cost against the shared daily budget, including the allowed input and maximum output. Make the check and reservation atomic across instances. Afterward, reconcile the reservation with reported usage where available. If a request's billing result is unknown, don't immediately assume it cost nothing.

This is an app-level guard, not a guaranteed account-wide billing ceiling. Pricing, delayed usage reports, calls made outside this endpoint, and in-flight requests can affect the final bill. Check your provider's documentation and dashboard for any spending limit or budget alert it offers, and verify whether a limit actually stops requests or only sends a warning.

Log the user or key ID, time, request outcome, and available usage or estimated cost. Avoid logging API keys, full prompts, or generated content by default. Use the logs to spot unusual usage and to compare your estimates with provider reports.

Limits change as the app grows, so ask your coding agent to keep all of this in one module you can find and adjust. If you're picking a plan for that agent, there's [10% off GLM coding plans](https://z.ai/subscribe?ic=R0K78RJKNW).

## Ask your agent, then test the result

Give your coding agent the request below, but review the changes before shipping. Ask it to identify the storage and provider-specific settings it needs instead of inventing them.

```
Inspect my app's paid AI endpoint and propose changes before editing.

Add server-side authentication using existing accounts or individually
issued client keys. Keep the provider key on the server.

Add shared per-user and secondary IP rate limits. On rate-limit rejection,
return HTTP 429 with an accurate Retry-After header.

Reject oversized inputs, set the provider's maximum output tokens, and
enforce an atomic daily per-user request quota in shared storage.

Add an app-wide daily budget reservation and reconciliation mechanism,
a server-configured kill switch, and minimal per-user usage logs. Fail
closed for paid calls if the shared quota or budget check is unavailable.

List assumptions, provider-specific limitations, changed files, and tests.
Do not log secrets or full prompts. Do not deploy or change paid settings.
```

Daily counter sketch:

```
  day = quota_day_in_configured_timezone()
  allowed = atomic_increment_if_below(user_id, day, daily_limit)
  if not allowed:
      reject_before_paid_call()
  else:
      continue_to_budget_check()
```

In staging, use your own account and a test provider setup or stub. Send a controlled burst at your own endpoint: confirm that requests over the limit receive the intended response and that rejected calls never reach the paid API.

Test concurrent requests near the quota and budget boundaries, an oversized input, missing credentials, the kill switch, and unavailable counter storage. Check the logs and provider dashboard against what you sent. If a test fails, fix it and repeat before release.

There's [more on our blog](https://jackgross-five.vercel.app) about shipping small apps with a coding agent.

## Checklist

- Is the AI endpoint authenticated, with the paid key kept server-side?
- Do per-user and IP limits return `429` with an accurate `Retry-After`?
- Are input size, output tokens, and daily user requests capped?
- Are quota and budget checks atomic and shared across instances?
- Can the kill switch stop new paid calls, and does the alert reach you?
- Have you tested the limits in staging and checked the usage logs?

If this saved you a surprise bill, a small tip on the post keeps these guides coming.
