Skip to content

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

ResourceDefault LimitWindow
Spin requests (POST /api/partner/v1/spin)300 per minuteFixed 60-second window
All other partner API calls600 per minuteFixed 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 × limit requests 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:

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests allowed in the current window300
X-RateLimit-RemainingRequests remaining in the current window287
X-RateLimit-ResetUnix timestamp when the window resets1711497600

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
json
{
  "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 CodeDescription
KS-4291Spin rate limit exceeded — too many spin requests
KS-4292API 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.

typescript
// 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:

typescript
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:

typescript
// 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:

  1. Incoming spin requests go into a queue.
  2. A worker pulls from the queue at a steady rate (e.g., 4 per second for a 300/minute limit).
  3. 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

KINGSTONE by Predigy Inc.