Security and rate limits
Onflow Ads runs on money and on other people's channels, so most of what protects it is invisible until you bump into it. This page lists what you can actually meet: the limits that answer "Too many attemptsβ¦", the lockouts, the protections around the Telegram bot and around the links in ads, and the rules an administrator is held to. Nothing here is a conduct action β hitting a limit never touches your reliability score or your account standing. The one conduct consequence this page describes is the deliberate misuse of a developer API key, and it says so where it does.
Every figure is the shipped default read from the platform's configuration. Two of them β the default write limits β can be retuned live by an operator, and where that matters it is said.
All of the counters below live in one store. If that store is unreachable, the request is allowed, not refused. A protection quietly not firing for a minute is a far smaller harm than a fault locking every legitimate member out of the site β so no limit on this page can ever be the reason the site is unusable during an incident.
The three layers of rate limitingβ
Three layers sit on top of each other. A request has to clear all three.
| Layer | What it counts | Default |
|---|---|---|
| The site-wide ceiling | Every request from one network address, apart from static files and a few exempt paths | 300 per minute per IP address |
| The write limiter | Every request that changes something β a form submitted, an order placed, a setting saved β counted per IP address and, when you are signed in, per account | 90 per minute per IP address Β· 240 per minute per account |
| Per-action budgets | A tighter, purpose-shaped allowance on the actions that cost the platform something β sending an email, moving money, generating with AI | Listed below |
The write limiter has tighter tiers for particular kinds of request:
| Kind of write | Limit |
|---|---|
| Sign-in, sign-up, verification codes, password, two-factor, first-email attachment, Telegram linking | 10 per minute per IP address |
| Wallet, checkout, purchases, withdrawals, payouts, auction bids, plan and add-on purchases, Boost orders and subscriptions, exchange ads | 30 per minute per account |
| Uploads β images, media, identity documents | 20 per minute per IP address and per account |
| The two payment-provider callbacks | 600 per minute per IP address |
| The Telegram bot's own calls into the website | 600 per 5 minutes per IP address |
The Boost developer API (/api/boost/v1) | 600 per minute per IP address, and no per-account bucket at all β the per-key budget below is the real one |
An API call carries a key rather than a session, so it is counted on a fourth budget of its own: one counter per key, on a fixed one-minute window. The size of that budget is your plan's requests-per-minute figure, banded by your reliability score and, where an administrator has set one, an override or a calls-per-day ceiling β all of it stated on The Boost API.
The two IP figures above sit outside it and are abuse ceilings only. Several keys can leave one server, so counting an integration by address rather than by key would punish whichever key happened to be last. Note that the site-wide 300-per-minute ceiling still applies to that address like any other, so a single machine driving several keys can meet it before any key's own budget runs out.
A key's own refusal comes back in the API's shape β a 429 carrying {"error": "Rate limit exceeded."} β and, unlike the layers above it, without a Retry-After header. Space your calls out on your own clock; the window is a fixed minute, so a burst that empties it waits out the remainder.
A limited request answers HTTP 429 with a Retry-After header. The message you see is the general one β "Too many attempts. Please wait a minute and try again." β and it deliberately does not say which layer you hit.
Scripts, styles, images, fonts, the documentation assets, the service worker and the offline page are never counted against the site-wide ceiling. Neither are the health probe, robots.txt, the sitemap, the web manifest, or the brand assets. So a page that loads forty files does not spend forty of your three hundred.
Anything counted per IP address is counted per network. An office, a campus, a cafΓ© or a mobile carrier can put hundreds of people behind one address, so if you hit a per-IP limit without doing anything unusual, someone else on your network almost certainly triggered it. Switching to mobile data or another network clears it immediately. The platform reads your address from the connection itself β a client cannot forge one in a header to escape a bucket.
Signing in and codesβ
| Action | Limit | Counted per |
|---|---|---|
| Creating an account | 5 per hour | IP address |
| Signing in with email and password | 10 per minute | IP address |
| Signing in with email and password | 5 wrong attempts per 15 minutes | the pair of email address and IP address |
| Sign-in attempts against one email from anywhere | 40 per 15 minutes, then a human challenge is required where one is configured | email address |
| Submitting a verification or reset code | 20 per minute | IP address |
| Wrong codes | 5 per verification session Β· 10 per rolling hour per email, then a 6-hour lock on the email and the IP address | session Β· email |
| Pressing Resend code | 8 per minute | IP address |
| Codes issued to one address | 6 per hour | email address |
| Requesting a password reset | 5 per hour | IP address |
| Requesting a password reset | 3 per 15 minutes | the pair of email address and IP address |
| Password resets requested for one email from anywhere | 12 per 15 minutes, after which the usual "if an account existsβ¦" reply is returned without sending a code β or a human challenge is required where one is configured | email address |
| Setting the new password at the end of a reset | 10 per hour | IP address |
| Submitting a two-factor code at sign-in | 20 per minute, with the same 5-per-session and 10-per-hour wrong-code counters and the same 6-hour lock | IP address |
| Starting a two-factor setup | 10 per hour | IP address |
| Enabling or disabling two-factor, or using a recovery code, from your account page | 10 per 15 minutes per account Β· 20 per minute per IP address. Wrong codes share the sign-in counter: 10 in an hour locks the email and the IP for 6 hours | account Β· IP address |
| Signing in with Google, Apple or Telegram | 10-minute round trip; the Telegram Mini-App handshake accepts 30 per 5 minutes per IP address | IP address |
If the five-attempts rule were counted on the email address alone, anyone who knew your address could lock you out of your own account by typing wrong passwords at it. Counting on the pair means a stranger's attempts fill a stranger's bucket, not yours. The per-email backstop above it β 40 for sign-in, 12 for password reset β is the safety net against a distributed attack, and it never locks you out: it asks for a human challenge, or for a reset it simply stops sending codes.
The full walk-through of the code counters, the 6-hour lock and what each message means is on Limits and lockouts.
Account and connection actionsβ
| Action | Limit | Counted per |
|---|---|---|
| Saving your profile name | 20 per hour | IP address |
| Attaching a first email (Telegram-created accounts) | 10 per hour | IP address |
| Pressing Connect Telegram | 10 per 10 minutes | account |
| Claiming a real email on the connection screen | 6 per 15 minutes | account |
| Choosing your permanent marketplace role | 10 per hour | IP address |
| Reading your reliability ledger | 60 per minute | account |
| Appealing a reliability penalty | 3 per day | account |
| Registering a browser for push notifications | 10 per hour per IP address Β· 20 per hour per account Β· at most 20 browsers per account. Only https endpoints on the major browsers' push services are accepted, and a browser already registered to another account cannot be taken over | IP address Β· account |
Moneyβ
| Action | Limit | Counted per |
|---|---|---|
| Starting a wallet top-up | 20 per hour per account Β· 40 per hour per IP address | account Β· IP address |
| Requesting a withdrawal | 10 per hour per account Β· 20 per hour per IP address | account Β· IP address |
| Requesting a top-up refund | 10 per hour per account Β· 20 per hour per IP address | account Β· IP address |
| Buying or renewing a plan | 10 per hour per account Β· 20 per hour per IP address | account Β· IP address |
| Buying the External Links add-on | 10 per hour | account |
| Paid Promotions checkout | 20 per hour per account and per IP address, plus a one-use key on every checkout so a double-tap can never charge twice | account Β· IP address |
| Bidding in an auction Β· buy-now Β· withdrawing a bid | 60 Β· 20 Β· 30 per hour | account |
| Confirming or cancelling an order | 60 per hour each | account |
| Claiming a payout Β· instant payout | 60 Β· 40 per hour | account |
| Placing a Boost order | 30 per hour per account Β· 60 per hour per IP address | account Β· IP address |
| Boost batches Β· refills Β· cancellations Β· subscriptions and their actions | 12 Β· 20 Β· 20 Β· 20 per hour | account |
| Submitting an exchange ad Β· cancelling one | 10 Β· 20 per hour | account |
| Raising a fraud concern | 20 per hour | account |
Every one of these takes the account from your signed-in session, never from the request, so nobody can move money on an account they are not signed in to.
Public forms and the AI toolsβ
| Action | Limit |
|---|---|
| The contact form | 5 per 10 minutes per IP address |
| Opening a support ticket | 6 per 10 minutes per IP address |
| Messaging on a ticket Β· closing Β· rating | 40 Β· 10 Β· 6 per 10 minutes per ticket |
| Onflow AI support chat | 20 per 10 minutes per IP address; signed-out visitors also 40 per day |
| Screenshots in the support chat | 30 uploads per 10 minutes per IP address; 3 per message, 4 MB each |
| Emailing yourself the guide | 5 per hour per IP address Β· 3 per hour per recipient |
| AI text Β· AI images (the studio, the composer, the copilots) | 20 per 5 minutes Β· 10 per 5 minutes per account, on top of your plan's daily and monthly allowance |
Developer API keysβ
A Boost API key is a password that spends your wallet, so it is kept like one: only a hash of it is stored, it is shown in full exactly once when it is minted, and it can never be recovered β only revoked and replaced. Every authenticated call it makes is logged for 90 days with the key, the action asked for, the outcome, why it was refused when it was, and the calling address.
Two different things stop a key working, and they are deliberately separate. You revoke a key yourself from the Developer page β do that the moment one leaks, because anyone holding it can place orders billed to you. We block one when it is being abused: that is a Master administrator's decision, taken behind a fresh password proof, carrying a written reason you are shown, and it can cover every key the account holds at that moment in one act. A block lives apart from your own revoke switch, so revoking the key and minting another does not clear it β support is the route back.
A key is also refused with nobody touching it whenever the account behind it may not act β banned or suspended, wallet overdrawn, or frozen while a fraud concern is open β and the refusal names which. That much is a gate, not a conduct action. Confirmed misuse of a key is the conduct action: sharing or reselling one, or scripting fraud through it, moves your reliability score, and only after a person has confirmed it. Every refusal, with the exact sentence it returns, is listed on The Boost API.
The links in adsβ
The links the platform publishes in ads are public, and their counts pay people, so they are guarded:
- Taps on a link in an ad (our short
onflowads.com/rr/β¦ad links β every link in an ad is one now β and the older forms that still work) are counted at most 20 per reader per link per day; uniques are one per reader per link per day regardless. A link-preview fetcher or crawler β the software that draws the card under a link β is served the redirect so the preview renders, but is never counted; nor are uptime monitors, requests with no browser named, requests that only check the link is there, and browser prefetches. - Bursts are flagged and never counted: one reader tapping far faster than a person reads, one network address sending an unusual flood, many readers from one small block of network addresses on one link in a short time, and any address an administrator has banned. Every flagged tap is still recorded, with the reason, for review.
- A reader is never turned away. A popular post can send thousands of readers from one mobile-carrier address, and every one of them is sent on to the ad; the counting happens after the redirect is sent, so it can neither slow a tap down nor fail it. If a link's destination cannot be looked up at that moment, the reader sees a short "Opening your link" page that tries the same link again a few seconds later, rather than being sent somewhere else.
- A made-up link costs almost nothing to refuse. Every short link code ever issued is known from memory, so an invented code costs at most a share of one database check a second, however many arrive. The signed links issued before the codes are refused on their signature alone, and the signing keys can be rotated without breaking a link that is already published.
- Landings reported by the Onflow tag on the advertiser's own page are deduplicated the same way.
- Conversions reported by the pixel are capped at 20 per reader per link per day and 500 per link per hour, and a reported value can be at most 10Γ the placement's charge (never below a $1,000 cap, never above $100,000; $100,000 where the charge is unknown), so nobody can write an advertiser's numbers for them.
- Referral link clicks (
onflowads.com/i/β¦) are counted at most 20 times per network address per code per day; the attribution cookie is still set beyond that, so a real signup is never lost. - A Telegram destination goes through the same short link, so its taps are held to the same limits as every other tap. Joins are attributed by Telegram itself through a per-campaign invite link, and recorded as reported; they are an indicator rather than an audited figure, and nothing you are charged, refunded or guaranteed is computed from them.
Full detail on what is counted and what is never stored: Links and results.
The Telegram bot's calls into the websiteβ
The bot never decides anything about money, tiers, reliability or moderation β it asks the website, or records what it saw. Every call it makes is signed:
- The body carries a timestamp and a single-use nonce, and the signature β made with a secret both sides hold β covers the method, the address being called and the whole body, so a signed call is good for that one address only. The website refuses a call whose timestamp is more than 5 minutes off, a nonce it has seen in the last 15 minutes, a body over 64 KB, or a signature that does not verify.
- The answers are signed too. The website signs every answer to a verified call, tied to that call's nonce, and the bot refuses an answer whose signature does not match β so nothing between the two halves can change a reply on its way back.
- Without the shared secret both sides fail closed: the bot does not call at all, and the website answers 503 to anything that tries. The same applies if the website cannot check a nonce at that moment β it refuses rather than accept a call it cannot prove is fresh. In that state a cross-promotion post or an exchange placement still goes out β with its links published as written and uncounted β and a booking preview or a moderation tap tells the person to use the website instead.
- The connection is watched from both ends. The bot reports in once a minute and sends a signed test call every few minutes whose only purpose is to prove the two halves still agree on the secret, the clock and the address. The website's status page shows when the bot last checked in, whether its signed calls are being accepted, and how far behind the queues of recorded facts have fallen β so a broken connection is visible rather than looking like a quiet day. Repeated refusals raise an alert on both sides.
- Moderation taps are relayed, not decided. When a reviewer taps Approve or Decline on a Subscriber Exchange card in Telegram, that tap is relayed to the website, which verifies who tapped, decides, refunds where a decline requires it, and notifies the member. A Paid Promotions listing review opens a signed, single-purpose confirmation page on the website. If the relay is down, the reviewer decides on the website's admin console.
- A "See why & appeal" or "Get the post here" button carries a signed, expiring token; the website checks the signature and that the Telegram account tapping it is the one entitled to. A token that leaks in a forwarded message is inert in anyone else's hands. Accepting or declining a booking is not among these calls at all: that moves a deposit and writes your reliability ledger, so it happens on the website, in a signed-in session.
- A connection link asks before it connects. The one-time link that attaches your Telegram to your account shows you which account it belongs to and binds only after you confirm β a link can be forwarded, so holding one is not treated as consent β and it never merges two accounts.
The bot has limits of its own inside Telegram, so a script driving one account cannot turn a button into a loop: 20 updates per 10 seconds per user; 10 per minute on the deep links that open a campaign, a placement or a connection; 3 per minute per booking on Get the post here; 6 per minute on post-review taps; 10 per minute on campaign clicks. Over budget, a button answers "Slow down a little and try again in a moment." and a message is simply dropped. Its own web endpoints (the branded ad landing and the live campaign view) allow 120 requests per minute per path per address. These buckets refill continuously, so a double-tap is forgiven and only a loop is clamped; like everything else here, they fail open.
What every page shipsβ
Every response from onflowads.com carries the standard hardening headers: X-Content-Type-Options: nosniff; X-Frame-Options: DENY with a matching frame-ancestors 'none', so no other site can frame a page of ours and trick you into clicking through it; Referrer-Policy: strict-origin-when-cross-origin; a Permissions-Policy that denies camera, microphone, geolocation and similar by default; cross-origin opener and resource policies scoped so that sign-in popups still work and the documentation site can embed assets; and Strict-Transport-Security for two years including subdomains, so a browser that has visited once never tries plain HTTP again.
A Content-Security-Policy ships in report-only mode: the browser reports violations, and nothing is blocked yet. The policy already names every third party a page legitimately loads β the card-checkout provider on the top-up page, the fonts, the sign-in widgets, the support chat and the optional advertising pixels an operator can switch on. It stays in report-only until a stretch of real traffic has gone by with no legitimate violation left to fix, because enforcing a policy that is one origin short blanks a page rather than logging a line.
Your session is an opaque token in a cookie marked HttpOnly, Secure and SameSite=Lax β scripts on a page cannot read it, it is never sent over plain HTTP, and another site cannot ride on it. A session lasts 48 hours, or 30 days with Remember me. Passwords are stored with Argon2. Every request that changes something is also checked for where it came from: a request carrying an Origin or Referer from another site is refused with "Cross-origin request blocked."
Sign-in protection: the 6-hour lockβ
Ten wrong codes in a rolling hour on one email address β across verification, password reset, two-factor at sign-in and two-factor changes on your account page β locks that email address and the IP address for 6 hours. Five wrong codes in one session does the same. Nothing lifts it early: not support, not a new browser, not signing up again. Full detail and the exact messages are on Limits and lockouts.
What an administrator has to proveβ
The people who run the platform are held to limits too, and they are worth knowing because they are part of what protects your money:
- Every destructive administrative action β wiping the database, restarting the service β is allowed 3 times an hour per administrator, and only when that administrator's Master password was entered within the last 10 minutes. Otherwise the action is refused and the console asks for the password again. Five wrong passwords at that prompt lock it for 6 hours. Every attempt, successful or not, is written to the security log.
- Saving the operational settings is allowed 20 times an hour and needs the same fresh password.
- A discretionary reliability adjustment is capped at Β±2.00 points per call and 10 calls an hour per reviewer, needs a written reason, and is written to your ledger where you can appeal it.
- Resolving a fraud concern, moving a score, freezing an account, restoring or forgiving a penalty and setting a plan are all Master-only. View-only administrators can read, and can work the support desk, but cannot change any of these.
- Administrators cannot edit or delete ledger history. Every reversal is a new entry pointing at the old one.
- The admin console loads no analytics, no cookie banner and no third-party scripts at all.
Automated-access blocking and the anti-spam challengeβ
The site can block crawler, scraper and script traffic, and the contact form can carry an invisible human check. Both are described, with the messages they produce, on Fraud and bot protection. A bare page reading only Forbidden. is a block on your network address, not on your account β see Account standing.
Reporting a security problemβ
Do not send a vulnerability through the support chat or the contact form. The published, machine-readable route is the security.txt file, described under Reporting a security issue. Good-faith researchers who stay inside the boundaries there will not be pursued.
If something goes wrongβ
| What you see | What it means | What to do |
|---|---|---|
| "Too many attempts. Please wait a minute and try again." | A per-minute or per-hour cap, or the write limiter | Wait a minute; on a shared network, switch networks |
| "Too many incorrect codes. For your security this is locked for 6 hours." | The code lockout | Wait it out. Nothing lifts it early |
| A human challenge appears on sign-in or password reset | The per-email backstop tripped β probably somebody else hammering your address | Complete the challenge and carry on; your account is not locked |
| "Cross-origin request blocked." | The request came from another site, a browser extension or an embedded frame | Open onflowads.com directly and retry |
| A push-notification registration is refused | The browser's push endpoint is not on a recognised push service, you have 20 browsers registered, or that browser is already registered to another account | Use a mainstream browser; remove an old registration |
| A Telegram button answers "Slow down a little and try again in a moment." | You tapped faster than the bot's per-user bucket allows | Wait a moment and tap once |
| A Get the post here button is missing from a booking DM, or the bot tells you to use the website | The bot cannot sign its call to the website right now | Open the booking on the website β nothing is lost |
An API key that worked yesterday answers 403 or 402 | The gate refused the account behind it, or an administrator blocked the key | Read the message β it names which. The Boost API lists all of them |
Relatedβ
- Limits and lockouts β the sign-in, code and connection limits, message by message
- Fraud and bot protection β the delivery monitor, the exclusivity check, the challenge and the crawler shield
- Account standing β the states that block an account, and the connection block that is not one
- Bot vs Website: Who Does What β why the bot only ever asks and reports
- Trust and safety β escrow, deposits, delivery proof and the security disclosure route
- The Boost API β the per-key rate, every refusal the gate returns, and what gets a key blocked