Skip to main content

Caching and freshness

The plan catalog is CDN-cached. Treat it as eventually consistent: a change made in Kamee will not appear in your copy immediately, and there is no way to force it to.

The actual numbers

LayerDirectiveMeaning
Your HTTP client / browsermax-age=300A response is fresh for 5 minutes.
Kamee's CDNs-maxage=3600The edge treats its copy as fresh for 1 hour.
Kamee's CDNstale-while-revalidate=86400For 24 hours past that, the edge serves the stale copy immediately and refreshes in the background.

The practical upshot: a change typically appears within an hour, but you can be served a copy up to roughly 25 hours old while the cache revalidates behind the scenes.

The cache key is the discipline query parameter plus your API key, so partners never receive each other's cached responses.

How often to poll

Once an hour is plenty. The catalog is a curated library that changes on the order of days, not minutes.

Polling faster does not get you fresher data — it just returns the same cached response more often. Polling on every page view is the mistake to avoid: it adds latency for your users and gains nothing.

Fetch on a schedule, store the result, and render from your own store.

Cache-busting does not work

This is the one that surprises people. Adding a random parameter will not get you a fresh response:

# Returns the same cached copy as the plain request.
curl -H "X-Api-Key: $KAMEE_API_KEY" \
"https://kamee.fit/api/plans?t=1721300000"

The cache is keyed only on discipline and your API key, so unrecognised parameters are ignored and cannot vary the key. If you need to confirm the origin has your change, wait out the window — or ask us.

generatedAt will not tell you either

generatedAt is stamped when the origin builds the response, not when it is delivered. A cached copy carries the timestamp from when it was originally generated, which can be roughly 25 hours earlier.

So generatedAt is not a freshness signal for your cache. Use your own fetch time to decide when to re-fetch, and treat generatedAt as provenance metadata only.

const ONE_HOUR = 60 * 60 * 1000;
let cache = {plans: null, fetchedAt: 0};

export async function getPlans() {
if (cache.plans && Date.now() - cache.fetchedAt < ONE_HOUR) {
return cache.plans;
}

try {
const res = await fetch('https://kamee.fit/api/plans', {
headers: {'X-Api-Key': process.env.KAMEE_API_KEY},
});
if (!res.ok) return cache.plans ?? [];

const {plans} = await res.json();
cache = {plans, fetchedAt: Date.now()};
return plans;
} catch {
return cache.plans ?? [];
}
}

Note it returns the previous copy on failure rather than an empty list. See Errors for why that matters.