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 succeeded
  • 201 Created — Resource created successfully
  • 400 Bad Request — Invalid request (validation error)
  • 401 Unauthorized — Missing or invalid authentication
  • 403 Forbidden — Insufficient permissions or license denied
  • 404 Not Found — Resource doesn't exist
  • 409 Conflict — Resource conflict (e.g., duplicate key)
  • 429 Too Many Requests — Rate limit exceeded
  • 500 Internal Server Error — Server error

Common Error Codes

Authentication Errors

CodeDescription
UNAUTHORIZEDMissing or invalid API key / session token
FORBIDDENValid credentials but insufficient permissions

Validation Errors

CodeDescription
VALIDATION_ERRORRequest body or query parameter validation failed
INVALID_POLICYPolicy structure is invalid or has conflicting settings

Resource Errors

CodeDescription
NOT_FOUNDResource doesn't exist
DUPLICATE_KEYLicense key already exists
LIMIT_EXCEEDEDPlan 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 CodeDescription
LICENSE_NOT_FOUNDLicense key doesn't exist
PRODUCT_MISMATCHLicense does not belong to the specified product
LICENSE_REVOKEDLicense has been revoked (status is REVOKED or FROZEN)
LICENSE_EXPIREDLicense has passed its fixed expiration date
LICENSE_EXPIRED_RELATIVELicense expired based on days-after-activation
HWID_MISMATCHHardware ID doesn't match the bound device (sticky mode)
HWID_LIMIT_EXCEEDEDToo many distinct hardware IDs (limit mode)
IP_MISMATCHIP doesn't match the bound address (sticky mode)
IP_LIMIT_EXCEEDEDToo many distinct IPs within the time window
IP_REGION_MISMATCHIP region not allowed (region-based limiting)
CONCURRENCY_LIMIT_EXCEEDEDToo many active sessions
HWID_BLACKLISTEDThe hardware ID is on the product's blacklist
IP_BLACKLISTEDThe 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 Requests
  • 500 Internal Server Error
  • 502 Bad Gateway
  • 503 Service Unavailable
  • 504 Gateway Timeout

Do not retry these errors:

  • 400 Bad Request — fix the request
  • 401 Unauthorized — fix authentication
  • 403 Forbidden — check permissions or license status
  • 404 Not Found — resource doesn't exist
  • 409 Conflict — resolve the conflict

Best Practices

  1. Always check the ok field — don't assume success based on HTTP status alone
  2. Handle reasonCode for license validation — provide user-friendly messages
  3. Implement retry logic — for transient errors with exponential backoff
  4. Log errors — include error codes and reason codes for debugging
  5. Respect rate limits — implement client-side throttling if needed