Skip to main content

Guides

Error Handling

Handle validation errors gracefully, implement retry logic, and build resilient integrations.

1. Validation Codes

Every validation response includes a code field indicating the result. Handle each code appropriately in your application.

CodeMeaningRecommended Action
VALIDLicense is valid and activeGrant access
EXPIREDLicense has passed its expiration dateShow renewal prompt
SUSPENDEDLicense has been suspended by the vendorShow support contact
REVOKEDLicense has been permanently revokedBlock access, show notice
BANNEDLicense has been bannedBlock access permanently
NOT_FOUNDNo license exists with the provided keyPrompt for valid key
FINGERPRINT_NOT_FOUNDMachine fingerprint not activated for this licenseTrigger activation flow
MACHINE_LIMIT_EXCEEDEDMaximum machine activations reachedPrompt to deactivate a device
HEARTBEAT_REQUIREDMachine heartbeat is required but missing or expiredSend heartbeat, then retry
USES_EXCEEDEDLicense has exceeded its maximum validation countUpgrade or contact vendor
ENTITLEMENTS_MISSINGRequired entitlements not attached to this licenseUpgrade to unlock features

2. HTTP Error Codes

The API uses standard HTTP status codes. The key distinction: 4xx errors are client errors (do not retry), 5xx errors are server errors (safe to retry).

HTTP status codes
# HTTP Status Codes
#
# 400 Bad Request      — Invalid input (malformed JSON, missing fields)
# 401 Unauthorized     — Missing or invalid API key / license key
# 403 Forbidden        — Valid auth but insufficient permissions
# 404 Not Found        — Resource does not exist
# 409 Conflict         — Invalid state transition (e.g., revoking an already revoked license)
# 422 Unprocessable    — Valid JSON but fails business rules
# 429 Too Many Requests — Rate limit exceeded (check Retry-After header)
# 500 Internal Error   — Server error (retry with backoff)

3. Retry Strategy

Only retry 5xx (server errors) and 429 (rate limit) responses. Never retry 4xx errors — they indicate a client-side problem that retrying will not fix.

Python

retry.py
from licentric import Licentric, ApiError, RateLimitError
import time

client = Licentric(api_key="lk_live_your_key_here")

def validate_with_retry(
    license_key: str,
    max_retries: int = 3
) -> dict:
    """Validate with automatic retry for transient errors."""
    for attempt in range(max_retries):
        try:
            return client.validate(
                key=license_key,
                fingerprint=client.fingerprint()
            )

        except RateLimitError as e:
            # 429 — wait for Retry-After duration
            wait = e.retry_after or (2 ** attempt)
            time.sleep(wait)

        except ApiError as e:
            if e.status_code >= 500:
                # 5xx — server error, retry with backoff
                time.sleep(2 ** attempt)
            else:
                # 4xx — client error, do NOT retry
                raise

    raise Exception("Max retries exceeded")

TypeScript

retry.ts
import {
  Licentric,
  ApiError,
  RateLimitError,
} from "@licentric/sdk";

const client = new Licentric({ apiKey: "lk_live_your_key_here" });

async function validateWithRetry(
  licenseKey: string,
  maxRetries = 3
) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await client.validate({
        key: licenseKey,
        fingerprint: client.fingerprint(),
      });
    } catch (error) {
      if (error instanceof RateLimitError) {
        const wait = error.retryAfter ?? 2 ** attempt;
        await sleep(wait * 1000);
        continue;
      }
      if (error instanceof ApiError && error.statusCode >= 500) {
        await sleep(2 ** attempt * 1000);
        continue;
      }
      throw error; // 4xx — do not retry
    }
  }
  throw new Error("Max retries exceeded");
}
Rate limiting
When you receive a 429 response, always respect the Retry-After header. Ignoring it may result in extended throttling or temporary ban.

4. SDK Error Types

The SDKs throw typed exceptions so you can handle specific error cases cleanly.

Python

error_types.py
from licentric import (
    Licentric,
    ValidationError,     # Invalid license key or validation failure
    AuthenticationError, # Invalid or missing API key
    RateLimitError,      # 429 Too Many Requests
    ApiError,            # All other API errors (base class)
)

client = Licentric(api_key="lk_live_your_key_here")

try:
    result = client.validate(
        key="DSK-XXXX-XXXX-XXXX-XXXX",
        fingerprint=client.fingerprint()
    )
except AuthenticationError:
    # Invalid API key — check your configuration
    log_error("API key is invalid or expired")
except RateLimitError as e:
    # Slow down — respect the rate limit
    log_warning(f"Rate limited. Retry after {e.retry_after}s")
except ValidationError as e:
    # License validation failed
    log_info(f"Validation failed: {e.code}")
except ApiError as e:
    # Unexpected API error
    log_error(f"API error {e.status_code}: {e.message}")

TypeScript

error-types.ts
import {
  Licentric,
  ValidationError,
  AuthenticationError,
  RateLimitError,
  ApiError,
} from "@licentric/sdk";

const client = new Licentric({ apiKey: "lk_live_your_key_here" });

try {
  const result = await client.validate({
    key: "DSK-XXXX-XXXX-XXXX-XXXX",
    fingerprint: client.fingerprint(),
  });
} catch (error) {
  if (error instanceof AuthenticationError) {
    logError("API key is invalid or expired");
  } else if (error instanceof RateLimitError) {
    logWarning(`Rate limited. Retry after ${error.retryAfter}s`);
  } else if (error instanceof ValidationError) {
    logInfo(`Validation failed: ${error.code}`);
  } else if (error instanceof ApiError) {
    logError(`API error ${error.statusCode}: ${error.message}`);
  }
}

5. Graceful Degradation

Never let a licensing error crash your application. Build a fallback chain: try online validation first, then cached results, then offline license files.

graceful.py
from licentric import Licentric, ApiError

client = Licentric(
    api_key="lk_live_your_key_here",
    cache_ttl=900  # 15-minute cache
)

def validate_gracefully(license_key: str) -> bool:
    """Never crash the app due to licensing errors."""
    try:
        result = client.validate(
            key=license_key,
            fingerprint=client.fingerprint()
        )
        return result.valid

    except ApiError:
        # API down — check cache
        cached = client.get_cached_result(license_key)
        if cached is not None:
            return cached.valid

        # No cache — check offline license file
        try:
            return client.validate_offline(
                certificate=load_license_file(),
                fingerprint=client.fingerprint()
            ).valid
        except Exception:
            pass

        # All fallbacks exhausted — allow or deny based on policy
        # Tip: allow access temporarily to avoid frustrating users
        return True  # or False for strict enforcement
Strict vs. lenient enforcement
Choose your fallback policy based on risk tolerance. For security software, deny access when all fallbacks fail. For productivity tools, consider allowing temporary access to avoid frustrating users during outages.