API Errors
Understanding how GeckoGuard handles errors and how to handle them in your application.
Error Response Format
All error responses follow this structure:
{
"ok": false,
"error": {
"message": "Human-readable error message",
"code": "ERROR_CODE"
}
}
License validation errors use a slightly different format:
{
"ok": false,
"allow": false,
"reasonCode": "HWID_MISMATCH",
"message": "Hardware ID does not match bound device"
}
HTTP Status Codes
200 OK— Request succeeded201 Created— Resource created successfully400 Bad Request— Invalid request (validation error)401 Unauthorized— Missing or invalid authentication403 Forbidden— Insufficient permissions or license denied404 Not Found— Resource doesn't exist409 Conflict— Resource conflict (e.g., duplicate key)429 Too Many Requests— Rate limit exceeded500 Internal Server Error— Server error
Common Error Codes
Authentication Errors
| Code | Description |
|---|---|
UNAUTHORIZED | Missing or invalid API key / session token |
FORBIDDEN | Valid credentials but insufficient permissions |
Validation Errors
| Code | Description |
|---|---|
VALIDATION_ERROR | Request body or query parameter validation failed |
INVALID_POLICY | Policy structure is invalid or has conflicting settings |
Resource Errors
| Code | Description |
|---|---|
NOT_FOUND | Resource doesn't exist |
DUPLICATE_KEY | License key already exists |
LIMIT_EXCEEDED | Plan limit reached (e.g., max licenses for your tier) |
License Validation Reason Codes
These appear as reasonCode in the /v1/licenses/authorize response when allow: false:
| Reason Code | Description |
|---|---|
LICENSE_NOT_FOUND | License key doesn't exist |
PRODUCT_MISMATCH | License does not belong to the specified product |
LICENSE_REVOKED | License has been revoked (status is REVOKED or FROZEN) |
LICENSE_EXPIRED | License has passed its fixed expiration date |
LICENSE_EXPIRED_RELATIVE | License expired based on days-after-activation |
HWID_MISMATCH | Hardware ID doesn't match the bound device (sticky mode) |
HWID_LIMIT_EXCEEDED | Too many distinct hardware IDs (limit mode) |
IP_MISMATCH | IP doesn't match the bound address (sticky mode) |
IP_LIMIT_EXCEEDED | Too many distinct IPs within the time window |
IP_REGION_MISMATCH | IP region not allowed (region-based limiting) |
CONCURRENCY_LIMIT_EXCEEDED | Too many active sessions |
HWID_BLACKLISTED | The hardware ID is on the product's blacklist |
IP_BLACKLISTED | The IP address is on the product's blacklist |
Error Handling Example
async function validateLicense(productId, licenseKey, hwid) {
const response = await fetch('https://api.geckoguard.com/v1/licenses/authorize', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ productId, licenseKey, hwid })
});
const result = await response.json();
if (result.allow) {
return { valid: true, expiresAt: result.effectiveExpiresAt };
}
// Handle specific denial reasons
switch (result.reasonCode) {
case 'LICENSE_NOT_FOUND':
return { valid: false, error: 'Invalid license key' };
case 'LICENSE_EXPIRED':
case 'LICENSE_EXPIRED_RELATIVE':
return { valid: false, error: 'License has expired' };
case 'LICENSE_REVOKED':
return { valid: false, error: 'License has been revoked' };
case 'HWID_MISMATCH':
case 'HWID_LIMIT_EXCEEDED':
return { valid: false, error: 'This license is bound to a different device' };
case 'HWID_BLACKLISTED':
case 'IP_BLACKLISTED':
return { valid: false, error: 'Access denied' };
case 'CONCURRENCY_LIMIT_EXCEEDED':
return { valid: false, error: 'Too many active sessions. Close other instances first.' };
default:
return { valid: false, error: result.message || 'Validation failed' };
}
}
Rate Limiting
When rate limited, you'll receive a 429 response:
{
"ok": false,
"error": {
"message": "Rate limit exceeded",
"code": "RATE_LIMIT_EXCEEDED"
}
}
Response headers include:
Retry-After: 60
X-Ratelimit-Limit: 120
X-Ratelimit-Remaining: 0
X-Ratelimit-Reset: 1640995200
Retry Strategy
Implement exponential backoff for rate-limited and transient errors:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
const delay = retryAfter * 1000 * Math.pow(2, i);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}
Retry Guidelines
Retry these errors with exponential backoff:
429 Too Many Requests500 Internal Server Error502 Bad Gateway503 Service Unavailable504 Gateway Timeout
Do not retry these errors:
400 Bad Request— fix the request401 Unauthorized— fix authentication403 Forbidden— check permissions or license status404 Not Found— resource doesn't exist409 Conflict— resolve the conflict
Best Practices
- Always check the
okfield — don't assume success based on HTTP status alone - Handle
reasonCodefor license validation — provide user-friendly messages - Implement retry logic — for transient errors with exponential backoff
- Log errors — include error codes and reason codes for debugging
- Respect rate limits — implement client-side throttling if needed