Recipes

Cache brand packs in your own backend

Reduce uncached-call costs by caching brand packs server-side. brandRNA already caches at $0/cached-call but local cache adds latency wins.

brandRNA caches brand packs internally at $0 per cached call, with a 24h TTL. That's already cheap. But every cache hit still costs you ~80ms of HTTP round-trip latency — non-trivial if you're personalising every pageview at 1k+ RPS. A local cache in your own backend cuts that to a few millisecond DB or Redis lookup.

This recipe shows when to cache, what to cache, and how to invalidate.

When it's worth it

Use caseLocal cache?Why
One-off scriptsNo$0 cached + simplicity wins.
Server-rendered landing pagesMaybeWorth it if p99 latency matters.
1k+ RPS personalisationYes80ms → 2ms is meaningful at scale.
Multi-region deploymentYesAvoid cross-region brandRNA hops.
Edge functionsYes (KV / D1)Keep brand packs co-located with your renderer.

Match brandRNA's own cache: 24 hours. Rebrands and redesigns are rare; a daily refresh is more than fresh enough for most use cases.

If you operate in a fast-moving vertical (crypto, fashion, news) and care about same-day rebrands, drop the TTL to 6h. There's no advantage to going below brandRNA's 24h ceiling — your cache will just hit the brandRNA cache, which is still 24h old.

Schema suggestion

CREATE TABLE brand_pack_cache (
  domain        text PRIMARY KEY,
  pack          jsonb NOT NULL,
  fetched_at    timestamptz NOT NULL DEFAULT now(),
  expires_at    timestamptz NOT NULL,
  source_cached boolean NOT NULL  -- whether brandRNA returned cached:true
);
CREATE INDEX ON brand_pack_cache (expires_at);
KEY:   brandpack:{domain}
VALUE: <serialized JSON pack>
TTL:   86400 (24h)

Use SET key value EX 86400 to atomically write + expire.

For Cloudflare KV / Workers D1 / Vercel Edge Config:

await KV.put(`brandpack:${domain}`, JSON.stringify(pack), {
  expirationTtl: 86400,
});

Implementation

Read-through cache

const KEY = process.env.BRANDRNA_API_KEY!;

export async function getCachedPack(domain: string) {
  const cached = await db.query.brandPackCache.findFirst({
    where: eq(schema.brandPackCache.domain, domain),
  });
  if (cached && cached.expiresAt > new Date()) {
    return cached.pack;
  }
  const fresh = await fetchFromBrandRNA(domain);
  await db.insert(schema.brandPackCache).values({
    domain,
    pack: fresh,
    expiresAt: new Date(Date.now() + 86400_000),
    sourceCached: fresh.metadata.cached === true,
  }).onConflictDoUpdate({
    target: schema.brandPackCache.domain,
    set: {
      pack: fresh,
      fetchedAt: new Date(),
      expiresAt: new Date(Date.now() + 86400_000),
    },
  });
  return fresh;
}

async function fetchFromBrandRNA(domain: string) {
  const res = await fetch(
    `https://api.brandrna.com/api/v1/pack/${domain}`,
    { headers: { Authorization: `Bearer ${KEY}` } },
  );
  if (!res.ok) throw new Error(`brandRNA ${res.status}`);
  return res.json();
}

Python equivalent

import os
import time
import httpx

KEY = os.environ['BRANDRNA_API_KEY']
TTL = 86400  # 24h

async def get_cached_pack(domain: str, db, cache) -> dict:
    cached = await cache.get(f"brandpack:{domain}")
    if cached:
        return cached
    async with httpx.AsyncClient(timeout=30) as client:
        r = await client.get(
            f"https://api.brandrna.com/api/v1/pack/{domain}",
            headers={"Authorization": f"Bearer {KEY}"},
        )
        r.raise_for_status()
        pack = r.json()
    await cache.set(f"brandpack:{domain}", pack, ex=TTL)
    return pack

Invalidation

Two triggers worth wiring up:

  1. Manual override. Expose an admin endpoint that drops the cache row for a single domain — useful when you spot a rebrand before our cache rolls over.
  2. brandRNA metadata.cached: false. When this field is false, brandRNA just performed a fresh extraction. That's a strong signal the domain changed, so refresh proactively.
if (fresh.metadata.cached === false) {
  // brandRNA re-extracted: cache it locally with full TTL
  await db.update(schema.brandPackCache)
    .set({ pack: fresh, fetchedAt: new Date(), expiresAt: new Date(Date.now() + 86400_000) })
    .where(eq(schema.brandPackCache.domain, domain));
}

Don't cache 404 / 5xx responses. Failures are usually transient (Cloudflare interstitial, scraper timeout) — cache them and you'll serve stale errors for a full TTL.

What's next

On this page