Skip to main content

Rate Limits

LumoAuth applies rate limits to specific authentication and provisioning flows to protect against abuse (credential stuffing, push bombing, mail bombing, and high-volume provisioning) and to ensure service availability for all organizations.

Rate Limit Headers​

Rate limiting is enforced per flow at the endpoint level, plus one per-credential limit on the management and decision APIs. Ordinary 2xx responses do not carry X-RateLimit-* headers.

When a flow's limit is exceeded the server responds with 429 Too Many Requests and a Retry-After header indicating how many seconds to wait:

429 Response Headers
Retry-After: 30
HeaderDescription
Retry-AfterSeconds to wait before retrying (only present on 429 responses)

Limits by Endpoint​

The limits below apply to the hosted service. They are set per deployment and cannot be changed by an organization.

FlowDefault limitWindow
OAuth token endpoint100 requestsper 60 seconds
OAuth authorize endpoint60 requestsper 60 seconds
Other OAuth protocol endpoints (fixed)PAR 60, CIBA bc-authorize 60, device authorization 30, introspection 100, revocation 100, dynamic client registration 10per 60 seconds
General API requests1,000 requestsper 60 seconds
Per credential: Admin API, /api/v1/authz/**, Zanzibar expand, ABAC check-bulk, /me/**600 requests per API key, access token or session userper 60 seconds
SCIM API500 requestsper 60 seconds
SCIM bulk10 requestsper hour
Login attempts (portal login, /orgs/{orgId}/login, magic link, push-auth login)5 attempts per IP and per emailper 15 minutes
Hosted OAuth login form (POST /orgs/{orgId}/api/v1/oauth/login/submit)same login_attempts budget: 5 per IP and per emailper 15 minutes
Hosted OAuth JSON login (POST /orgs/{orgId}/api/v1/oauth/login/json)same login_attempts budget: 5 per IP and per emailper 15 minutes
Forgot password3 per IP + email and 5 per email address from any IP; throttled requests get the same neutral responseper hour
Current-password re-entry (change password or e-mail, delete account, forced change)5 per user; failures also count toward account lockoutper 15 minutes
MFA challenge (hosted page and /mfa/challenges API, shared per-user budget)10; the API answers 429 rate_limited with Retry-After on create and verifyper 15 minutes
Registration5per hour
Resend verification e-mail (/verify-email/resend)3 per address and 20 per IP (fixed)per hour
Webhook deliveries100per 60 seconds
Push auth (initiate / respond / activation)strict per-flow ceilingsper window

Login throttling and account lockout​

The three login rows above share one budget, the login_attempts limiter, keyed separately by client IP and by normalised e-mail address. Both keys are consumed on every attempt. After 5 failed attempts the account is also locked for 15 minutes (the same lockout that protects the portal form login), and IPs with excessive failures are blocked temporarily.

The two hosted OAuth login endpoints are the ones your own login UI talks to when it uses the LumoAuth hosted login page or the JSON login. How a rejection looks depends on the endpoint:

EndpointWhen throttled or locked
/oauth/login/submit (form post)Redirect back to the login page with the generic "Invalid credentials" message. The reason is not disclosed to the caller; it is recorded in the audit log as throttled_or_locked.
/oauth/login/json429 Too Many Requests with the body {"status":"rate_limited"}

The form logins (/login, /orgs/{orgId}/login) spend the per-email unit atomically before the password is compared, so parallel guesses cannot all slip in under the lockout count; a spent budget returns 429 with the locked-account page.

A successful login clears the rate-limiter windows for that IP and e-mail, so a user is not punished for earlier typos.

Per-credential limit​

Per-IP limits cannot stop one credential spread across many addresses. So the programmatic management and decision surfaces also count requests per credential:

SurfacePaths
Admin API/orgs/{orgId}/api/v1/admin/**
Authorization decision point/api/v1/authz/**
Zanzibar expand/orgs/{orgId}/api/v1/zanzibar/expand
ABAC bulk checks/orgs/{orgId}/api/v1/abac/check-bulk
Self-service API/orgs/{orgId}/api/v1/me and /me/**

The key is the API key (by id), the access token's user plus client_id, or the signed-in user for browser sessions. The limit is a sliding window of 600 requests per 60 seconds. When it is spent the request is refused before any other check runs:

HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json

{"error": "rate_limited", "message": "Too many requests for this credential", "status": 429}

Spread batch jobs over time, or give independent jobs their own API keys (each key has its own budget).

Handling 429​

429 Too Many Requests
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please retry after 30 seconds.",
"status": 429,
"retryAfter": 30
}

Implement exponential back-off with jitter when handling 429 responses:

Exponential Back-off (JavaScript)
async function requestWithRetry(fn, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const res = await fn();
if (res.status !== 429) return res;
const retryAfter = parseInt(res.headers.get("Retry-After") ?? "1", 10);
const jitter = Math.random() * 1000;
await new Promise((r) => setTimeout(r, retryAfter * 1000 + jitter));
}
throw new Error("Max retries exceeded");
}
tip

For bulk provisioning (e.g. importing large user sets), use the SCIM Bulk endpoint to reduce the number of individual API calls by combining multiple operations in a single request.