Error Handling
The KINGSTONE SDK throws KingstoneApiError for all API error responses. This class provides structured information about what went wrong, making it easy to handle different error types programmatically.
The KingstoneApiError Class
class KingstoneApiError extends Error {
statusCode: number; // HTTP status code (401, 404, 429, etc.)
errorCode?: string; // Machine-parseable code (e.g., "KS-4001")
message: string; // Human-readable error description
retryable: boolean; // true for 429 and 5xx errors
}Every API error is mapped to an instance of KingstoneApiError. The errorCode uses the format KS-XXXX where the number indicates the category:
| Range | Category | Examples |
|---|---|---|
KS-40xx | Authentication | Missing key, invalid key |
KS-41xx | Validation | Bad request body, wager out of range |
KS-42xx | Game/Resource | Game not found, queue not ready |
KS-43xx | Settlement | Already acknowledged, dispute conflict |
KS-429x | Rate Limit | Spin rate limit, API rate limit |
KS-50xx | Server | Internal error, queue service error |
Basic Error Handling
import { KingstoneClient, KingstoneApiError } from '@kingstone/sdk';
const client = new KingstoneClient({
apiKey: 'ks_sandbox_your_key_here',
sandbox: true,
});
try {
const result = await client.spin({
gameId: 1,
playerId: 'player-abc',
wagerUsd: 0.25,
});
console.log(`Payout: $${result.outcome.payoutUsd}`);
} catch (error) {
if (error instanceof KingstoneApiError) {
console.error(`[${error.errorCode}] ${error.message} (HTTP ${error.statusCode})`);
} else {
console.error('Network or unexpected error:', error);
}
}Handling Specific Error Codes
Use the errorCode to respond differently to different errors:
try {
await client.spin({ gameId: 1, playerId: 'p1', wagerUsd: 0.25 });
} catch (error) {
if (!(error instanceof KingstoneApiError)) throw error;
switch (error.errorCode) {
case 'KS-4001':
// Missing API key — configuration problem
console.error('API key not configured');
break;
case 'KS-4002':
// Invalid or revoked API key
console.error('API key is invalid — contact Predigy for a new key');
break;
case 'KS-4103':
// Wager out of range
console.error('Wager rejected — check min/max/increment limits');
break;
case 'KS-4201':
// Game not found
console.error('Game does not exist — check the gameId');
break;
case 'KS-4291':
// Spin rate limit
console.error('Too many spins — slow down and retry');
break;
default:
console.error(`Unhandled: ${error.errorCode} — ${error.message}`);
}
}Retry Logic for Transient Errors
The retryable property is true for errors that are likely temporary:
- 429 — Rate limit exceeded. Wait and try again.
- 5xx — Server error. The issue is on KINGSTONE's side and may resolve on its own.
All other errors (authentication, validation, resource not found) are not retryable — sending the same request again will produce the same error.
Exponential Backoff Example
import { KingstoneApiError } from '@kingstone/sdk';
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelayMs = 1000,
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
const isRetryable = error instanceof KingstoneApiError && error.retryable;
const hasRetriesLeft = attempt < maxRetries;
if (isRetryable && hasRetriesLeft) {
const delay = baseDelayMs * Math.pow(2, attempt);
console.log(`Retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
// Not retryable, or out of retries
throw error;
}
}
throw new Error('Unreachable');
}
// Usage
const result = await withRetry(
() => client.spin({ gameId: 1, playerId: 'p1', wagerUsd: 0.25 }),
3, // max retries
1000, // start with 1 second delay
);Handling by HTTP Status
If you prefer to handle errors by HTTP status instead of error codes:
try {
await client.getPlayer('nonexistent-id');
} catch (error) {
if (!(error instanceof KingstoneApiError)) throw error;
if (error.statusCode === 401) {
// Authentication failed
console.error('Check your API key');
} else if (error.statusCode === 404) {
// Resource not found
console.error('Player has never spun');
} else if (error.statusCode === 429) {
// Rate limited
console.error('Slow down — check X-RateLimit-* headers');
} else if (error.statusCode >= 500) {
// Server error — retry
console.error('Server error — retry with backoff');
} else {
// Other client error (400, 409, etc.)
console.error(`Client error: ${error.message}`);
}
}Timeout Errors
If a request exceeds the configured timeoutMs (default: 30 seconds), the SDK throws a KingstoneApiError with status code 408:
try {
await client.spin({ gameId: 1, playerId: 'p1', wagerUsd: 0.25 });
} catch (error) {
if (error instanceof KingstoneApiError && error.statusCode === 408) {
console.error('Request timed out — the server may be overloaded');
}
}You can adjust the timeout when creating the client:
const client = new KingstoneClient({
apiKey: 'ks_sandbox_your_key_here',
sandbox: true,
timeoutMs: 10000, // 10 seconds
});Network Errors
If the request fails at the network level (DNS resolution, connection refused, etc.), the SDK throws the native fetch error, not KingstoneApiError. Always check instanceof before accessing error-specific properties:
try {
await client.listGames();
} catch (error) {
if (error instanceof KingstoneApiError) {
// API returned an error response
console.error(`API error: ${error.errorCode}`);
} else if (error instanceof TypeError) {
// Network-level failure (fetch throws TypeError)
console.error('Network error — check connectivity and base URL');
} else {
console.error('Unexpected error:', error);
}
}Next Steps
- See the full Error Codes reference for all KS-XXXX codes
- Review Rate Limits to understand throttling behavior
