Rate Limits
KINGSTONE enforces per-partner rate limits to ensure fair usage and protect the system from abuse. Rate limits are applied at the API key level, meaning all requests from the same partner share the same limit pool.
Default Limits
| Resource | Default Limit | Window |
|---|---|---|
| Spin requests | 300 per minute | Sliding window |
| All other API calls | 600 per minute | Sliding window |
These defaults apply to all partners. Predigy can adjust limits per partner based on expected traffic volume. Contact your account representative if you need higher limits.
Rate Limit Headers
Every response includes rate limit information in the headers:
| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window | 300 |
X-RateLimit-Remaining | Requests remaining in the current window | 287 |
X-RateLimit-Reset | Unix timestamp when the window resets | 1711497600 |
Use these headers to track your usage and avoid hitting the limit.
What Happens When You Hit the Limit
When you exceed the rate limit, the API returns:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711497600{
"message": "Spin rate limit exceeded",
"errorCode": "KS-4291"
}The Retry-After header tells you how many seconds to wait before retrying.
Error Codes
| Error Code | Description |
|---|---|
KS-4291 | Spin rate limit exceeded — too many spin requests |
KS-4292 | API rate limit exceeded — too many non-spin API requests |
Best Practices
Respect the Headers
Read X-RateLimit-Remaining before each request. If the remaining count is low, slow down proactively instead of waiting for a 429 response.
// Example: Check rate limit headers from a raw fetch response
const response = await fetch(url, { headers: { 'X-API-Key': apiKey } });
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
if (remaining < 10) {
console.warn(`Rate limit almost exhausted: ${remaining} requests remaining`);
}Implement Exponential Backoff
When you receive a 429 response, wait and retry with increasing delays:
import { KingstoneApiError } from '@kingstone/sdk';
async function spinWithBackoff(client, request, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await client.spin(request);
} catch (error) {
if (error instanceof KingstoneApiError && error.statusCode === 429 && attempt < maxRetries) {
const delay = 1000 * Math.pow(2, attempt); // 1s, 2s, 4s
console.log(`Rate limited. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
}Spread Traffic Evenly
If you need to process a batch of spins (for example, during a high-traffic promotion), spread them evenly over time instead of bursting them all at once. A simple approach:
// Process 200 spins over 60 seconds instead of all at once
const delayBetweenSpins = 300; // milliseconds
for (const player of players) {
await client.spin({ gameId: 1, playerId: player.id, wagerUsd: 0.25 });
await new Promise(resolve => setTimeout(resolve, delayBetweenSpins));
}Use a Request Queue
For production systems with variable load, use an in-memory request queue with a rate-limited consumer:
- Incoming spin requests go into a queue.
- A worker pulls from the queue at a steady rate (e.g., 4 per second for a 300/minute limit).
- Responses are matched back to the original request via a callback or promise.
This prevents bursts from triggering rate limits even during traffic spikes.
Sandbox vs. Production
Rate limits are the same in sandbox and production. This ensures your integration handles rate limiting correctly before going live.
Next Steps
- See Error Handling for retry patterns
- Review API Versioning for API stability guarantees
