Skip to main content

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

sdk_caching.py
from licentric import Licentric

# 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=client.fingerprint()
)

# Subsequent calls within 10 minutes use the cache
result = client.validate(
    key="DSK-XXXX-XXXX-XXXX-XXXX",
    fingerprint=client.fingerprint()
)  # No API call — served from cache

TypeScript

sdk-caching.ts
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: client.fingerprint(),
});

// Subsequent calls within 10 minutes use the cache
const cached = await client.validate({
  key: "DSK-XXXX-XXXX-XXXX-XXXX",
  fingerprint: client.fingerprint(),
}); // No API call — served from cache

2. Cache Storage Options

Choose a caching strategy based on your application type.

StrategyStoragePersistenceBest ForTTL
In-memoryProcess memoryLost on restartLong-running servers, desktop apps5-15 minutes
File-basedLocal filesystemSurvives restartsCLI tools, desktop apps, containers15-60 minutes
DatabaseRedis, SQLite, etc.Survives restartsMulti-instance servers, microservices5-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.

file_cache.py
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.

invalidation.py
# 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)
Recommended approach
Use TTL-based caching (5–15 minutes) as the default, and add webhook-based invalidation for time-sensitive use cases like subscription cancellations.

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.

fallback.py
from licentric import Licentric, ApiError

client = Licentric(
    api_key="lk_live_your_key_here",
    cache_ttl=600
)

def validate_with_fallback(license_key: str) -> bool:
    """Validate with graceful degradation on API failure."""
    try:
        result = client.validate(
            key=license_key,
            fingerprint=client.fingerprint()
        )
        return result.valid
    except ApiError:
        # API unreachable — check cache
        cached = client.get_cached_result(license_key)
        if cached is not None:
            return cached.valid

        # No cache — check offline license file
        return validate_offline_file("license.lic")