docs / best practices

Best Practices

How to use the Attestd SDKs efficiently. Cache policies, CI/CD patterns, async batching, and session stats so you stay within quota without sacrificing freshness.

cache policies

Choose a cache policy

Both SDKs cache check results in memory by product and version. The default is runtime. Cached hits do not count against your monthly quota.

PolicyTTLUse case
development24 hLocal loops. Avoid burning Free-tier quota while iterating.
runtime5 minProduction default. Fresh enough for deploy gates and agents.
ciinfiniteCI/CD runs. Deduplicate the same product+version within one job.
none0Raw mode. Every call hits the API. Useful for tests.

Python: Client(cache_policy="runtime"). JavaScript: new Client({ cachePolicy: 'runtime' }).

ci/cd integration

CI vs production

Use cache_policy="ci" in pipelines so a large SBOM does not re-fetch the same package versions. Use runtime in long-lived services.

ci_gate.py
import attestd

# Deduplicate within a CI run. Same product+version is fetched once.
with attestd.Client(api_key="YOUR_API_KEY", cache_policy="ci") as client:
    for product, version in sbom:
        result = client.check(product, version)
        if result.risk_state in ("critical", "high"):
            raise SystemExit(f"blocked: {product} {version}")

    print(client.stats())  # api_calls_made, cache_hits, calls_saved
python
import attestd

# Production default: 5-minute TTL. Fresh enough, quota-efficient.
client = attestd.Client(api_key="YOUR_API_KEY", cache_policy="runtime")
typescript
import { Client } from '@attestd/sdk';

// Production default: 5-minute TTL.
const client = new Client({
  apiKey: process.env.ATTESTD_API_KEY,
  cachePolicy: 'runtime',
});
async batching

Coalesce concurrent checks

Python AsyncClient coalesces concurrent check() calls within batch_window_ms (default 5) into one POST /v1/check/batch. For an explicit list of dependencies, call batch_check (Python) or checkBatch (JavaScript) instead.

fanout.py
import attestd
import asyncio

async def check_many(deps):
    async with attestd.AsyncClient(
        api_key="YOUR_API_KEY",
        cache_policy="runtime",
        batch_window_ms=5,
    ) as client:
        # Concurrent check() calls within the window coalesce into one batch.
        results = await asyncio.gather(
            *(client.check(p, v) for p, v in deps)
        )
        print(client.stats().batch_saves)
        return results
sbom.py
import attestd

deps = [("nginx", "1.20.0"), ("log4j", "2.14.1"), ("redis", "6.0.9")]

with attestd.Client(api_key="YOUR_API_KEY") as client:
    results = client.batch_check(deps)
quota efficiency

Session stats

Call stats() at the end of a deploy or CI job. Log calls_saved to confirm caching and batching are working.

python
stats = client.stats()
print(stats.api_calls_made)  # billed API calls this session
print(stats.cache_hits)      # results served from cache
print(stats.batch_saves)     # check() calls coalesced into a batch
print(stats.calls_saved)     # cache_hits + batch_saves
typescript
const stats = client.stats();
console.log(stats.apiCallsMade);
console.log(stats.cacheHits);
console.log(stats.callsSaved);
invalidation

When to invalidate

Invalidate a cache entry after a patch deploy, or when a webhook tells you a new advisory landed for that product. The next check fetches fresh data.

python
client.invalidate_cache("nginx", "1.20.0")
# Next check("nginx", "1.20.0") hits the API again.
typescript
client.invalidateCache('nginx', '1.20.0');
// Next check('nginx', '1.20.0') hits the API again.
related