Rate Limits
SingleSign enforces rate limits to ensure fair usage and platform stability. This page documents the limits for each endpoint, the response format when limits are exceeded, and best practices for staying within bounds.
Per-Endpoint Limits
Rate limits are applied per client ID (or per IP address for unauthenticated endpoints) using a sliding window.
| Endpoint | Rate Limit | Window |
|---|---|---|
GET /.well-known/openid-configuration | 100 requests | Per minute |
GET /oauth/authorize | 60 requests | Per minute |
POST /oauth/token | 120 requests | Per minute |
GET /oauth/userinfo | 120 requests | Per minute |
GET /oauth/jwks | 100 requests | Per minute |
POST /oauth/introspect | 120 requests | Per minute |
POST /oauth/revoke | 60 requests | Per minute |
Note: These limits represent reasonable defaults. If your application requires higher throughput, contact SingleSign support to discuss increased limits for your platform.
429 Response
When you exceed a rate limit, SingleSign returns an HTTP 429 Too Many Requests response:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1740000030
Response Body
{
"error": "too_many_requests",
"error_description": "Rate limit exceeded. Please retry after 30 seconds."
}
Response Headers
| Header | Type | Description |
|---|---|---|
Retry-After | integer | Number of seconds to wait before retrying. |
X-RateLimit-Limit | integer | The maximum number of requests allowed in the current window. |
X-RateLimit-Remaining | integer | The number of requests remaining in the current window. |
X-RateLimit-Reset | integer | Unix timestamp when the rate limit window resets. |
Handling Rate Limits
Exponential Backoff
When you receive a 429 response, wait before retrying. The recommended strategy is exponential backoff with jitter:
async function fetchWithRetry(
url: string,
options: RequestInit,
maxRetries = 3
): Promise<Response> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status !== 429) {
return response;
}
if (attempt === maxRetries) {
throw new Error("Rate limit exceeded after maximum retries.");
}
// Use Retry-After header if available
const retryAfter = response.headers.get("Retry-After");
let waitMs: number;
if (retryAfter) {
waitMs = parseInt(retryAfter, 10) * 1000;
} else {
// Exponential backoff: 1s, 2s, 4s + random jitter
waitMs = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
}
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
throw new Error("Unreachable");
}
Python Example
import time
import random
import requests
def fetch_with_retry(url, max_retries=3, **kwargs):
for attempt in range(max_retries + 1):
response = requests.request(**kwargs, url=url)
if response.status_code != 429:
return response
if attempt == max_retries:
raise Exception("Rate limit exceeded after maximum retries.")
retry_after = response.headers.get("Retry-After")
if retry_after:
wait = int(retry_after)
else:
wait = (2 ** attempt) + random.uniform(0, 1)
time.sleep(wait)
Best Practices
1. Cache Tokens
The most effective way to reduce API calls is to cache your access tokens and reuse them until they expire.
let cachedToken: { token: string; expiresAt: number } | null = null;
async function getAccessToken(): Promise<string> {
if (cachedToken && cachedToken.expiresAt > Date.now() + 60_000) {
return cachedToken.token;
}
const tokens = await exchangeCodeForTokens();
cachedToken = {
token: tokens.access_token,
expiresAt: Date.now() + tokens.expires_in * 1000,
};
return cachedToken.token;
}
2. Cache Discovery and JWKS Responses
The discovery endpoint (/.well-known/openid-configuration) and JWKS endpoint (/oauth/jwks) return data that changes infrequently. Cache these responses for at least 1 hour and refresh on a schedule.
let discoveryCache: { data: any; fetchedAt: number } | null = null;
const CACHE_TTL = 60 * 60 * 1000; // 1 hour
async function getDiscovery(): Promise<any> {
if (discoveryCache && Date.now() - discoveryCache.fetchedAt < CACHE_TTL) {
return discoveryCache.data;
}
const response = await fetch(
`https://api.singlesign.com/.well-known/openid-configuration?platform_guid=${platformGuid}`
);
const data = await response.json();
discoveryCache = { data, fetchedAt: Date.now() };
return data;
}
3. Use Refresh Tokens Instead of Re-Authenticating
Instead of sending users through the full authorization flow when their access token expires, use a refresh token to get a new access token. This reduces calls to both /authorize and /token.
4. Batch Operations
If your application needs to make many API calls, batch them where possible. For example, extract all needed claims from the /oauth/userinfo response in a single call rather than making repeated requests.
5. Monitor Rate Limit Headers
Proactively check X-RateLimit-Remaining to throttle your requests before hitting the limit:
function shouldThrottle(response: Response): boolean {
const remaining = parseInt(response.headers.get("X-RateLimit-Remaining") || "999", 10);
return remaining < 5;
}
6. Avoid Token Introspection on Every Request
If your server validates tokens on every API request, prefer local JWT validation (verify the signature and check exp) over calling /oauth/introspect. Reserve introspection for cases where you need real-time revocation checks.
Rate Limit Scope
| Scope | Description |
|---|---|
| Per Client ID | Authenticated endpoints (/token, /userinfo, /introspect, /revoke) are limited per client_id. |
| Per IP | Unauthenticated endpoints (/authorize, discovery, JWKS) are limited per source IP address. |
If you operate multiple instances of your application behind a load balancer, all instances share the same client-level rate limit.
Increasing Your Limits
If your application legitimately needs higher rate limits:
- Review the best practices above to ensure you're not making unnecessary requests.
- Contact SingleSign support with:
- Your
platform_guidandclient_id. - Which endpoint(s) need higher limits.
- Your expected request volume and traffic pattern.
- Why caching and other optimizations are insufficient.
- Your
Next Steps
- Endpoint Reference — Full API documentation.
- Errors Reference — Error codes and troubleshooting.
- Tokens & Claims — Cache tokens to reduce API calls.
Last updated: 2026-02-16
Related docs
- SingleSign Developer DocumentationLearn how to integrate SingleSign identity and authentication into your applications using OAuth 2.0 and OpenID Connect.
- the Endpoint Reference docsComplete API reference for all SingleSign OAuth 2.0 and OpenID Connect endpoints with methods, parameters, and curl examples.
- the Error Reference docsComplete reference of SingleSign OAuth error codes, error response format, and troubleshooting tips for each error.
- free identity providerSingleSign pricing in one line: sign-in is free, with no monthly-active-user meter. See exactly what is included, and what SingleSign Mail costs for businesses.
- Identity provider alternativesIdentity provider alternatives, by the provider you are leaving: what actually has to change in your code, and what does not.
- Auth0 comparisonSingleSign vs Auth0 compared on the difference that matters: who owns the account. A side-by-side table, then the three cases where each one is the right call.
- SingleSign vs OktaSingleSign vs Okta: Okta is built for workforce identity inside a company, SingleSign for consumer sign-in across applications.
- Firebase Auth comparisonSingleSign vs Firebase Authentication on lock-in, portability and consent, plus the cases where staying on Firebase is right.