How Flowo Rate Limits and Meters Its AI Backend
By Mykhailo Boichuk · Chief Technology Officer
In short
Flowo has no user accounts, so BBMM Technologies limits its AI backend per attested device. Three layers do the work: a short-lived token issued against Apple App Attest or Google Play Integrity, a per-minute rate limit shared by every AI route, and a daily quota counted atomically on the server. The app mirrors the server's numbers, and its purchase gates let the action through whenever nothing can be bought.
What the limits protect
Every limit in the Flowo backend exists to bound what one device can cost. Each AI request calls a paid language model. A voice note adds speech transcription, which is billed per second of audio, and the comments in our quota module put a voice request at roughly ten times the cost of a text one.
That is why voice has its own counter. A free plan gets 10 text requests and 3 voice notes per day. Flowo Pro gets 30 text requests and the same 3 voice notes. Voice is a cost ceiling on every plan, so the app never shows a paywall when the voice allowance runs out. It shows the reset time.
Pro is metered too. Our code states the reason directly: an unlimited paid plan puts no bound on what one subscriber can cost. Only development identities skip metering.
Identity without accounts
A limit needs a subject, and Flowo has no sign-up, so the subject is the device. Before any AI call, the app proves itself with platform attestation: Apple App Attest on iOS and Google Play Integrity on Android. The backend answers with a short-lived signed access token, and every other API route requires it.
This replaced two shared secrets that were compiled into the app. We removed them for three reasons recorded in the backend entry point: anyone could read them out of an Android package, they could not be revoked without a new app release, and they carried no identity, so per-device limits were impossible.
The token is signed with an asymmetric key, so verification needs only the public half. In production the server refuses to start if attestation is switched to a development mode or if the signing key is missing.
A revoked device receives a 403 response where an expired token receives a 401. A 401 tells the app to authenticate again, and a revoked device that kept doing so would loop forever.
Per-minute limits keyed by device
Our burst limit is keyed by the attested device identifier, and the IP address is only a fallback. We wrote down why IP is the wrong subject: behind a load balancer many callers can look like one address, carrier-grade NAT puts thousands of people behind one address, and an abuser can rotate addresses at no cost.
There are three separate budgets:
- One budget shared by every route that calls the language model, so spreading requests across endpoints gains nothing.
- A looser budget for the authentication routes, keyed by address because no device identity exists yet at that point.
- A budget for code redemption and referral routes, so they cannot be used to enumerate codes.
How the daily quota is counted
The daily quota is one row per device, per request family and per UTC day, incremented with an atomic conditional update in DynamoDB. The write succeeds only while the stored count is below the ceiling. Two requests racing across instances cannot both take the last unit.
The ceiling is passed in on every request and is never stored with the counter. It depends on the device's current plan, which the server reads from its own device registry on each call, so an upgrade applies on the next request. All four ceilings come from environment variables. A cost surprise becomes a configuration edit and needs no deploy.
Several rules keep the count fair to the person using the app:
- A request that fails with an error status has its unit refunded by the response middleware.
- One assistant message can trigger several model calls. The first call pays and opens a turn; a small fixed number of follow-ups ride on a server-issued turn token that expires within minutes and is worthless on another device.
- Dictation during onboarding spends no voice unit and has its own separate daily ceiling.
When the allowance is spent, the server answers 429 with the reset time. A subscriber who hits the Pro ceiling is never shown an upsell.
Request size and audio duration
A middleware caps the request body per route before any handler runs. JSON routes get a few kilobytes by default. Assistant chat, the purchase webhook and voice upload each have a larger cap. The check runs twice: against the declared length and against the bytes actually read. An oversized body gets a 413 and a machine-readable error code.
A size cap does not bound the cost of audio, because a small file of low-bitrate speech can run for many minutes and still spend one quota unit. The voice endpoint therefore reads the duration from the M4A container and rejects recordings longer than the app's own recording limit plus a few seconds of slack. The parser follows the declared box sizes and never scans for signatures, since a scan could be steered by the audio payload.
How the app mirrors the server
The app keeps a local copy of the daily counters so it can show how many requests are left and refuse a request it already knows will be refused. That copy lives in local preferences and resets on reinstall, which is the reason the server counter is the authority.
Every metered response carries the limit, the remaining count, the reset instant and the request family. The app overwrites its local copy with those values through a class called AiUsageGate. If the server's ceiling changes, the app adopts the new number on the next response.
Quota exhaustion has its own exception type in the client. It is never shown as a network failure.
Purchase gates that fail open
Flowo's subscription gates let the action through whenever nothing is purchasable. Free plans have weekly allowances on four features: 7 Recovery runs, 7 AI plans, 5 Braindump Tidy runs and 3 mentor conversations. Subtasks nest 2 levels deep without Pro.
When an allowance is spent, the gate records an analytics event named entitlement_gate_hit and then checks whether a paywall could show a real product. If monetization is disabled in the build, or the store returned no plans, the gate returns true and the feature runs. Blocking a person behind a paywall that cannot render would be a dead end with no exit.
Cost controls on the server fail closed. Purchase prompts in the app fail open. The server keeps metering either way.
Key takeaways
- Flowo's backend identifies callers by attested device, using Apple App Attest and Google Play Integrity, because the app has no user accounts.
- The per-minute rate limit is keyed by device identifier and shared across every route that calls the language model.
- Daily quotas are atomic conditional updates in DynamoDB, counted per device, per request family and per UTC day.
- Free plans get 10 text AI requests and 3 voice notes per day; Flowo Pro gets 30 text requests and the same 3 voice notes.
- A request that ends in an error status gets its quota unit refunded by the response middleware.
- Subscription gates in the app record the hit and allow the action whenever no product can be purchased.
Frequently asked questions
- How do you rate limit an API when users have no accounts?
- Flowo uses the device as the subject. The app proves itself with Apple App Attest or Google Play Integrity and receives a short-lived signed token. Rate limits and daily quotas are then keyed by the device identifier inside that token.
- Why is rate limiting by IP address a weak choice for a mobile app?
- Mobile carriers place thousands of people behind one address, so an IP limit punishes unrelated users together. An abuser can also change addresses at no cost. Flowo keys its limits by attested device and falls back to the address only on the routes that issue the device token.
- What are the daily AI limits in Flowo?
- A free plan allows 10 text AI requests and 3 voice notes per day. Flowo Pro allows 30 text requests and 3 voice notes. Counters reset at midnight UTC and the server's count is authoritative.
- What happens to my allowance if an AI request fails?
- The unit is returned. The Flowo backend reserves one unit before calling the model, and its response middleware refunds that unit when the request ends with an error status.
- What does it mean for a paywall gate to fail open?
- It means the gate allows the action when it cannot offer a purchase. In Flowo, if the build has monetization disabled or the store returns no plans, the gate records the event and lets the feature run. The server still applies its own daily quota.
References
About the author
Mykhailo Boichuk
Chief Technology Officer
Mykhailo builds the systems behind the apps. He works on macOS and iOS performance, and on the sync problems that come with software that has to run offline.