deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Paid AI feature shipped with no server-side paywall and hidden from paying users

A dev.to post recounts a paid AI analysis feature guarded only by a client-side credit check: unauthenticated API calls ran real Gemini requests for free while paying users could not find the feature.

Paid AI feature shipped with no server-side paywall and hidden from paying users

A developer has described launching a paid AI feature that failed in both directions at once: anyone who knew the right request shape could use it without paying, while the subscribers it was built for could not find it in the interface at all. The account, published on dev.to, concerns APEX Versus, a site that uses AI to settle hypothetical "who would win" matchups.

The site's Deep Analysis and Advanced Analysis options charge a few credits per run and are supposed to be unlimited on the Pro plan. Behind them sit real calls to Google's Gemini model, which means every unauthorised use burned paid API quota with no compensating revenue.

A paywall that only existed in the browser

According to the post, the credit check lived entirely in the frontend. The API route that actually performed the advanced comparison verified nothing: a POST request naming the advanced mode, sent with no authentication header, would trigger a genuine Gemini call and return the full output. There was no login requirement, no credit check and no daily cap on that route.

The developer confirmed the gap with a plain curl request and noted that the rate limiting which did exist had been attached to the basic comparison endpoint instead — protection placed on a route that attackers wanting the advanced mode never needed to touch.

A feature paying users could not see

The second bug sat in the interface. A line of JavaScript set the Deep Analysis card to hidden whenever the viewer was on a paid plan — the visibility condition was inverted. Separately, the Advanced Analysis button intended for Pro users had no click handler attached anywhere; the function behind it existed, but nothing ever invoked it, leaving it dead code from the day it was written.

The combined effect was the exact inverse of a working paywall: non-payers who constructed the right request got the deeper analysis indefinitely at no cost, while paying customers had no visible way to reach the thing they were funding.

One root cause behind both symptoms

The post traces both failures to a single oversight. The developer had earlier tightened Firestore security rules so that credit balances can only be written from server code using the admin SDK — a sound move that closed a path for clients to edit their own balance. But the older browser-side code that deducted a credit after a paid action was never removed or replaced. It kept running, kept failing, and because its error handling consisted of an empty catch block, a rejected write looked no different from a successful one. No log, no error, no signal to anyone.

The fix, tested against reality

The remedy, once diagnosed, was direct. The credit check and deduction moved fully server-side, into a Firestore transaction that runs before the Gemini call rather than after it. The dead client-side deduction function was deleted, the visibility condition was corrected, and the missing button was finally added.

Verification was done on a live paid account rather than by trusting the diff: the developer ran a Deep Analysis and watched the balance drop by exactly two credits, atomically and server-confirmed — which, the post notes, was the first successful deduction in the feature's existence.

Why it matters

The core observation generalises well beyond one side project: a check performed in the client and a check performed on the server are not interchangeable, even when they read the same variable. The browser-side version is advisory; the server-side version is the only one a determined user cannot sidestep. For anything that touches revenue — credits, quotas, entitlements — enforcement belongs on the backend, ideally inside a transaction that precedes the expensive model call instead of reconciling after the spend has already happened.

The failure modes here were also silent by construction: swallowed exceptions on one side, hidden interface elements on the other. Ordinary usage, routine testing and code review all missed them; only a deliberate audit of the codebase surfaced the problem. The practical advice the author offers to anyone operating a credits or paywall system is to search for every place a balance is checked and ask whether that check would still hold if a user bypassed the interface entirely and sent requests straight to the endpoint. If the answer leans on assumption rather than guarantee, there is no enforcement in place — only the appearance of it.

  • #api-security
  • #paywall
  • #gemini
  • #firebase
  • #server-side

Related posts