The Brand Object

Cache, refresh and pin a version

How old each brand context API's data can be, what its docs say about storing it, when to ask again, and how to record which context a piece of work used.

Store two dates with every response. The first date is the date of your request. The second date is the date when the vendor read the data, if the response gives it. Ask again on a schedule that suits the task. Keep the files, not the links. Maintained context is context prepared from sources added for the brand. For maintained context, store the version ID with the work that it produced.

Endpoint How the response says how old it is What the docs say about age and storage
Brandfetch Brand Context API meta.resolved_at Brandfetch returns a stored profile until it refreshes the profile. Brandfetch does not publish how often. The terms allow caching only by written agreement, for up to 30 days
Brandfetch Brand API Not stated The frequently asked questions on the pricing page say that you can cache responses for 30 days. You can cache shortcut image addresses with no time limit
Context.dev Brand and Styleguide cache_metadata.age_ms Context.dev uses a cached copy up to maxAgeMs, about 90 days by default. 0 reads the site again
Firecrawl branding Not stated By default, Firecrawl uses a cached copy up to 2 days old (maxAge). 0 reads the page again. Firecrawl charges a cached result like a fresh one
Logo.dev Brand API data.indexed_at, etag per file You can store anything that it returns. File links expire after about 24 hours. Compare etag to find out if an image changed
Rainbrand Brief skillId The Brand Skill version that the Brief came from. After you apply a brand edit, request a new Brief
Rainbrand Brand Skill skillId (REST), version_id (Model Context Protocol, MCP) Each version is a snapshot that does not change

A cache that keeps both dates

type Cached<T> = { value: T; requestedAt: string; readAt: string | null };
const store = new Map<string, Cached<unknown>>();

export async function cached<T>(
  key: string,
  maxAgeDays: number,
  load: () => Promise<{ value: T; readAt: string | null }>
): Promise<Cached<T>> {
  const hit = store.get(key) as Cached<T> | undefined;
  const age = hit ? (Date.now() - Date.parse(hit.readAt ?? hit.requestedAt)) / 86_400_000 : Infinity;
  if (hit && age < maxAgeDays) return hit;
  const { value, readAt } = await load();
  const entry = { value, requestedAt: new Date().toISOString(), readAt };
  store.set(key, entry);
  return entry;
}

// Age is counted from when Brandfetch read the site, not from when you asked.
// const ctx = await cached(`brandfetch-context:${domain}`, 30, async () => {
//   const c = await getContext(domain);
//   return { value: c, readAt: c.meta.resolved_at };
// });
import time
from datetime import datetime, timezone

_store: dict[str, dict] = {}

def cached(key: str, max_age_days: float, load):
    hit = _store.get(key)
    if hit:
        stamp = datetime.fromisoformat((hit["read_at"] or hit["requested_at"]).replace("Z", "+00:00"))
        if (time.time() - stamp.timestamp()) / 86400 < max_age_days:
            return hit
    value, read_at = load()
    hit = {"value": value, "requested_at": datetime.now(timezone.utc).isoformat(), "read_at": read_at}
    _store[key] = hit
    return hit

Brandfetch read the Linear profile on 16 October and returned it on 11 November. The profile was 26 days old when we got it. If you count a 30-day cache from the request, the cache keeps the profile until 11 December.

Recording which context a piece of work used

Keep the source and the version of the context next to the work that it produced. For a Rainbrand Brief, keep skillId. Observed context is context read from a company's public website. For observed context, keep the endpoint and the read date.

await saveOutput({
  output,
  brandContext: { endpoint: "Rainbrand Brief", briefId: brief.data.id, version: brief.data.skillId },
});

If a customer asks why last month's ad used the old tagline, this record gives you the answer.

Last updated 11 November 2026