Skip to main content

Rate Limits

Klubero SSO implements rate limiting to ensure service stability and prevent abuse.

Rate limits table​

All rate limits are applied per IP address.

EndpointLimitWindowPurpose
/connect/token10 requests15 minutesProtection against brute-force attacks on credentials
/api/twofactor/*5 requests5 minutesProtection against brute-force attacks on verification codes
/api/magiclink/send3 requests5 minutesPrevention of email spam
/connect/authorize30 requests1 minuteProtection against DoS and redirect URI enumeration

Response when the limit is exceeded​

When a rate limit is exceeded, the server returns HTTP status 429 Too Many Requests with a Retry-After header (the number of seconds until the next allowed request) and a short plain-text body:

HTTP/1.1 429 Too Many Requests
Retry-After: 300
Content-Type: text/plain

API calls quota exceeded! maximum admitted 10 per 15m.
note

The response is not in JSON format. To determine the wait time, always read the value of the Retry-After header (in seconds); do not rely on parsing the response body. The exact wording of the text body may vary depending on the configuration.

Handling rate limits​

  1. Check the response status: Look for HTTP 429
  2. Read the Retry-After header: It indicates the number of seconds to wait
  3. Implement exponential backoff: For automated systems
  4. Cache tokens: Reuse access tokens until they expire

Best practices​

Do​

  • Cache and reuse tokens until they expire
  • Use refresh tokens instead of re-authenticating
  • Implement proper error handling with backoff
  • Batch operations where possible

Don't​

  • Request a new token for every API call
  • Re-authenticate for every session
  • Retry immediately on failure
  • Make many sequential requests

Example: Token caching​

let tokenCache = {
accessToken: null,
expiresAt: null
};

async function getAccessToken() {
// Return the cached token if it's still valid (with a 60s buffer)
if (tokenCache.accessToken && tokenCache.expiresAt > Date.now() + 60000) {
return tokenCache.accessToken;
}

// Request a new token
const response = await fetch('/connect/token', {
method: 'POST',
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET
})
});

if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 60;
throw new Error(`Rate limited. Try again in ${retryAfter} seconds.`);
}

const data = await response.json();
tokenCache.accessToken = data.access_token;
tokenCache.expiresAt = Date.now() + (data.expires_in * 1000);

return tokenCache.accessToken;
}