What a refusal looks like, and how to wait
Every limit here refuses with 429. Most userate_limited, and put the wait in the refusal’s fix as “Retry after N seconds” — only the per-credential ceiling also sets a retry-after header. Mail Lath sends on your behalf has its own code, auth_send_limited, because it is a separate budget with a separate remedy.
A refused call is never stored against an Idempotency-Key, so retrying with the same key is safe and still cannot run the operation twice. Wait for the stated seconds, and back off exponentially when there are none.
Signing in
Starting a sign-in —auth.signin.start and auth.password.signin — is limited to 5 attempts per identifier and 30 per IP address, each in 10 minutes. The identifier is the email address or phone number, hashed before it is counted, so the table that holds the counts holds no addresses.
auth.passkey.signin.start and auth.identity.add count against the same per-address setting as starting a sign-in.
Finishing one — auth.link.complete, which is the page a magic link opens — allows 30 per IP address in 10 minutes. auth.session.exchange, which turns the handoff code that page returns into a session, allows the same.
Those three settings — per identifier, per address to start, per address to complete — are the ones you can change. The rest on this page are fixed.
Tune the sign-in limits
auth.settings.set takes rateLimits with startPerIdentifier, startPerIp and completePerIp, each a max from 1 to 100 and a windowSeconds from 60 to 86,400. Objects merge key by key, so sending only max keeps the window you had.
It needs a secret key with auth:write, and takes effect on the next request. Raise the per-address limits when many people sign in from one office or one mobile carrier’s address; lower the per-identifier limit when you would rather a person wait than an attacker keep guessing.
Creating accounts and users
A sign-in that would create a new user is counted per environment and IP address while bot protection is on, which it is by default — 5 an hour unless changed, settable asbotProtection.signupPerIp in auth.settings.set with the same max and windowSeconds ranges. Sign-ins by people who already have an account are not counted here, which is what lets an office sign in a hundred times without looking like a hundred sign-ups.
account.signup, which creates a Lath account, allows 30 an hour per IP address. account.member.accept allows 20 per IP address in 10 minutes.
Things a signed-in user does
Counted per user:auth.identity.add 5 an hour; auth.factor.totp.enroll 10 an hour; auth.factor.totp.activate 10 in 10 minutes; auth.passkey.register.start 10 an hour; auth.password.set 10 an hour; auth.org.create 10 an hour.
Counted per organization: auth.org.invite.create 50 an hour.
Counted per IP address, for the device flow a CLI or a TV uses: auth.device.start and auth.device.approve 20 in 10 minutes; auth.device.describe 30 in 10 minutes. The preference page a recipient opens from an email allows 60 reads and changes per IP address in 10 minutes.
Texts, and mail Lath sends for you
sms.send is limited to 10 texts a second and 500 an hour per environment. A call over either is refused rather than queued, so a job sending one text at a time should pace itself — or use an SMS broadcast, which is not counted against this limit.
Sign-in codes, magic links and invitations are mail Lath sends on your behalf, and they share a budget of 500 an hour and 5,000 a day per environment, refused with auth_send_limited. It does not apply to email.send. It exists because auth.signin.start works with a publishable key, which is public by design — so a burst that reaches it is more often a key being abused than real traffic, and developers.key.rotate replaces the key.
