Rate Limits
KINGSTONE enforces per-partner rate limits to ensure fair usage and protect the system from abuse. Limits are counted per partner account (keyed by your partner ID, not by individual API key) — all requests authenticated to the same partner share one limit pool, regardless of which API key issued them.
Default Limits
| Resource | Default Limit | Window |
|---|---|---|
Spin requests (POST /api/partner/v1/spin) | 300 per minute | Fixed 60-second window |
| All other partner API calls | 600 per minute | Fixed 60-second window |
The spin endpoint and all other partner endpoints have separate counters: a spin request counts only against the 300/min spin limit, and other calls count only against the 600/min API limit. These defaults apply to all partners; Predigy can adjust either limit per partner based on expected traffic volume — contact your account representative if you need higher limits.
Fixed window, not sliding. Each limit is a discrete 60-second window: the counter resets fully when the window elapses rather than rolling continuously. Because of this, up to
2 × limitrequests can succeed across a window boundary (the tail of one window plus the head of the next). Spread traffic evenly (see Best Practices) rather than relying on precise boundary timing.
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{
"status": "error",
"errorCode": "KS-4291",
"message": "Rate limit exceeded for spin requests",
"retryAfter": 12
}The Retry-After header (and the matching retryAfter field in the body) tells you how many seconds to wait before retrying — it counts down to the end of the current fixed window.
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 '@kingstoneapp/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
