Guides
Validation Caching
Reduce API calls and latency by caching validation results locally.
Why Cache Validation Results?
- Reduce latency — cached lookups are instant vs. network round-trips
- Reduce API calls — stay well within rate limits
- Partial offline support — serve cached results when the network is unavailable
- Better UX — no loading states for license checks in hot paths
1. SDK Built-in Caching
Both SDKs support TTL-based in-memory caching out of the box. Set the cache_ttl (Python) or cacheTtl (TypeScript) option when creating the client.
Python
from licentric import Licentric, fingerprint
# The SDK has built-in TTL-based caching
client = Licentric(
api_key="lk_live_your_key_here",
cache_ttl=600 # Cache validation results for 10 minutes
)
# First call hits the API
result = client.validate(
key="DSK-XXXX-XXXX-XXXX-XXXX",
fingerprint=fingerprint()
)
# Subsequent calls within 10 minutes use the cache
result = client.validate(
key="DSK-XXXX-XXXX-XXXX-XXXX",
fingerprint=fingerprint()
) # No API call — served from cacheTypeScript
import { Licentric } from "@licentric/sdk";
const client = new Licentric({
apiKey: "lk_live_your_key_here",
cacheTtl: 600, // Cache for 10 minutes
});
// First call hits the API
const result = await client.validate({
key: "DSK-XXXX-XXXX-XXXX-XXXX",
fingerprint: Licentric.fingerprint(),
});
// Subsequent calls within 10 minutes use the cache
const cached = await client.validate({
key: "DSK-XXXX-XXXX-XXXX-XXXX",
fingerprint: Licentric.fingerprint(),
}); // No API call — served from cache2. Cache Storage Options
Choose a caching strategy based on your application type.
| Strategy | Storage | Persistence | Best For | TTL |
|---|---|---|---|---|
| In-memory | Process memory | Lost on restart | Long-running servers, desktop apps | 5-15 minutes |
| File-based | Local filesystem | Survives restarts | CLI tools, desktop apps, containers | 15-60 minutes |
| Database | Redis, SQLite, etc. | Survives restarts | Multi-instance servers, microservices | 5-15 minutes |
3. File-Based Caching
For CLI tools and desktop apps that restart frequently, persist the cache to disk so it survives process restarts.
import json
import time
from pathlib import Path
class FileCache:
"""Persistent file-based cache for validation results."""
def __init__(self, cache_dir: str, ttl: int = 600):
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(parents=True, exist_ok=True)
self.ttl = ttl
def get(self, key: str) -> dict | None:
path = self.cache_dir / f"{key}.json"
if not path.exists():
return None
data = json.loads(path.read_text())
if time.time() - data["cached_at"] > self.ttl:
path.unlink() # Expired
return None
return data["result"]
def set(self, key: str, result: dict):
path = self.cache_dir / f"{key}.json"
path.write_text(json.dumps({
"result": result,
"cached_at": time.time()
}))
# Usage
cache = FileCache("~/.myapp/license_cache", ttl=900)4. Event-Driven Invalidation
For the most up-to-date validation results, use webhooks to invalidate the cache when a license changes. This gives you the speed of caching with near-real-time accuracy.
# Event-driven cache invalidation via webhooks
# When a license changes, clear its cached validation
def handle_webhook(event):
event_type = event["type"]
# These events mean the cached validation is stale
invalidation_events = [
"license.suspended",
"license.revoked",
"license.expired",
"license.updated",
"entitlement.attached",
]
if event_type in invalidation_events:
license_id = event["data"]["id"]
cache.invalidate(license_id)5. Fallback Behavior
When the API is unreachable and the cache contains a valid (non-expired) result, allow access based on the cached validation. This provides graceful degradation without requiring full offline license files.
from licentric import Licentric, LicentricError, fingerprint
# grace_period keeps serving a STALE cached result for a while after the
# fresh cache_ttl window expires; fail_mode decides what happens once even
# the stale cache is gone. validate() applies both automatically — no
# manual cache lookup needed.
client = Licentric(
api_key="lk_live_your_key_here",
cache_ttl=600, # 10-minute fresh cache
grace_period=1800, # serve a stale cached result for up to 30 minutes if the API is down
fail_mode="open", # allow access if the API is unreachable and no cache exists
)
def validate_with_fallback(license_key: str) -> bool:
"""Validate with graceful degradation on API failure."""
try:
result = client.validate(
key=license_key,
fingerprint=fingerprint()
)
return result.valid
except LicentricError:
# Cache + API both exhausted and fail_mode="open" above already
# covers that case — this only fires on a hard failure such as a
# signature-verification error. Deny access rather than fail open
# on an unexpected error shape.
return False