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, LicentricError, RateLimitError, fingerprint
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=fingerprint()
)
except RateLimitError as e:
# 429 — wait for Retry-After duration
wait = e.retry_after or (2 ** attempt)
time.sleep(wait)
except LicentricError 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,
LicentricError,
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: Licentric.fingerprint(),
});
} catch (error) {
if (error instanceof RateLimitError) {
const wait = error.retryAfter ?? 2 ** attempt;
await sleep(wait * 1000);
continue;
}
if (error instanceof LicentricError && 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,
fingerprint,
ValidationError, # Invalid license key or validation failure
AuthenticationError, # Invalid or missing API key
RateLimitError, # 429 Too Many Requests
LicentricError, # 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=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 LicentricError as e:
# Unexpected API error
log_error(f"API error {e.status_code}: {e.message}")TypeScript
import {
Licentric,
ValidationError,
AuthenticationError,
RateLimitError,
LicentricError,
} 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: Licentric.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 LicentricError) {
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, LicentricError, fingerprint
# The client already handles resilience: cache_ttl controls the fresh-cache
# window, grace_period controls how long a STALE cached result may still be
# served if the API is unreachable, and fail_mode controls what happens once
# both are exhausted. Configure this once instead of hand-rolling fallback code.
client = Licentric(
api_key="lk_live_your_key_here",
cache_ttl=900, # 15-minute fresh cache
grace_period=3600, # serve a stale cached result for up to 1 hour if the API is down
fail_mode="open", # allow access if the API is unreachable and no cache exists
)
def validate_gracefully(license_key: str) -> bool:
"""Never crash the app due to licensing errors.
validate() already checks the cache, retries transient failures, then
falls back to a stale cached result within grace_period before applying
fail_mode — no manual cache/offline fallback chain needed for the common case.
"""
try:
result = client.validate(
key=license_key,
fingerprint=fingerprint()
)
return result.valid
except LicentricError:
# Cache + API both exhausted (fail_mode="open" above already covers
# that case) — this branch only fires for a hard failure such as a
# signature-verification error. Fall back to an offline license file
# checked out ahead of time via client.checkout().
try:
with open("license.lic", "rb") as f:
offline = Licentric.verify_offline(
license_file_data=f.read(),
public_key="your-base64-ed25519-public-key", # from GET /api/v1/account/signing-key
)
return offline.valid and not offline.expired
except Exception:
# No offline file available — allow or deny based on policy.
# Tip: allow access temporarily to avoid frustrating users.
return True # or False for strict enforcement