Why Sign-In Attempts Are Rate Limited
In this article
Most people only learn about rate limits when they hit one: a message saying there have been too many attempts and to try again later. It can feel arbitrary, but the limits in Invoice Generator are deliberately set at different levels for different parts of the app. Once you know which requests share a limit and how long its window is, the message tells you exactly what happened.
Four Separate Limits, Each Counted per IP Address
The server keeps four independent counters, and each one is tracked per IP address:
- Authentication: 10 requests per hour by default. This covers sign-up, sign-in, the two-factor code check during login, and requests to resend a verification email.
- The main app: 1,000 requests per 15 minutes in production. This covers invoices, clients, payments, reports, expenses, time entries, workspaces, the client portal, and the developer API.
- The contact form: 10 submissions per hour.
- The image proxy, which loads remote logos for PDF rendering: 500 requests per 15 minutes.
Because these are separate counters, using up one doesn't affect the others. If you lock yourself out of sign-in after mistyping your password repeatedly, anyone on your network who is already signed in can keep working. Their requests count against the main app's limit, not the authentication one.
Why Authentication Gets the Strictest Limit
A person signing in legitimately needs one attempt, maybe two or three after a typo. Ten per hour is plenty for that. An automated password-guessing tool needs thousands of attempts. Capping the login endpoint at ten per hour per IP turns a fast guessing attack into one that would take years, without anyone noticing a difference in normal use.
The two-factor check is in the same group for the same reason. A six-digit code has only a million possible values. Without a limit, those could be tried quickly within the five-minute window the temporary login token is valid for. With the shared limit, an attacker who already has the password gets about ten guesses at the code before being blocked for an hour.
Resending the verification email is included because each resend sends a real email. Without a cap, the endpoint could be used to flood someone's inbox with verification messages they never requested.
Why the Main App's Limit Is So Much Higher
Normal use of the dashboard generates many requests. Loading a page can fetch invoices, clients, notifications, and the business profile at the same time, and a bulk action or a busy report adds more. A thousand requests per 15-minute window leaves a lot of room for heavy real use, while still blocking a runaway script or an integration stuck in a loop before it affects everyone else.
The Contact Form and Image Proxy
The contact form has its own 10-per-hour limit because each submission sends an email to the support team. Without it, the form would be an easy way to send spam. The image proxy has a separate 500-per-15-minute budget because PDF rendering can request the same remote logo several times. Giving it its own counter means generating documents doesn't use up your general app limit.
What You See When You Hit a Limit
Over the limit, the server returns HTTP 429 with a plain message such as "Too many authentication attempts, please try again after an hour." Responses also include the standard RateLimit headers, so an API integration can read how much of its budget remains and when the window resets, instead of guessing. Windows are fixed periods (an hour for authentication, 15 minutes for the app), so waiting out the stated time always clears the block.
A Note on Shared Networks
Because counting is per IP address, everyone behind the same public IP shares a budget. That might be an office, a co-working space, or a VPN exit node. That rarely matters for the main app, but ten sign-ins per hour can be used up if a whole team logs in at once from one office connection. If several people suddenly can't sign in at the same time, that's the most likely cause, and it clears itself within the hour.
Related Articles
How an Invoice Is Stored: One Row per Line Item, Plus a Full Copy
A saved invoice is stored as one database row per line item, linked by a shared group ID, with a full copy of the invoice on every row. Saving an edit replaces all of those rows in one transaction.
How Sign-Up and Email Verification Actually Work
Creating an account sets up a personal workspace in the same database transaction, the verification link lasts 24 hours, and clicking an expired one quietly sends you a fresh link instead of leaving you stuck.
How Your Logo Gets Stored When You Upload It
A logo pasted in as a remote URL gets fetched server-side, checked against a 2MB cap and a real image content type, and converted into a self-contained data URL before it's saved.
Why You Get Logged Out After an Hour
Every session token expires in exactly one hour, and there's no refresh mechanism behind the scenes extending it quietly — plus the one deliberate exception carved out for two-factor logins.
How Client Portal Links Expire
A portal link defaults to 30 days, checks expiry on every request rather than up front, and matches a client's documents by an exact, case-sensitive-safe comparison — not a database foreign key.
How Invoice View Tracking Actually Works
Two separate mechanisms record when a client opens your invoice — a running counter and a detailed event log — and a password-protected share link doesn't count as viewed until the password is entered.