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.
| Code | Meaning | Recommended Action |
|---|---|---|
| VALID | License is valid and active | Grant access |
| EXPIRED | License has passed its expiration date | Show renewal prompt |
| SUSPENDED | License has been suspended by the vendor | Show support contact |
| REVOKED | License has been permanently revoked | Block access, show notice |
| BANNED | License has been banned | Block access permanently |
| NOT_FOUND | No license exists with the provided key | Prompt for valid key |
| FINGERPRINT_NOT_FOUND | Machine fingerprint not activated for this license | Trigger activation flow |
| MACHINE_LIMIT_EXCEEDED | Maximum machine activations reached | Prompt to deactivate a device |
| HEARTBEAT_REQUIRED | Machine heartbeat is required but missing or expired | Send heartbeat, then retry |
| USES_EXCEEDED | License has exceeded its maximum validation count | Upgrade or contact vendor |
| ENTITLEMENTS_MISSING | Required entitlements not attached to this license | Upgrade 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
#
# 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
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
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");
}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
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
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.
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