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
| Layer | Directive | Meaning |
|---|---|---|
| Your HTTP client / browser | max-age=300 | A response is fresh for 5 minutes. |
| Kamee's CDN | s-maxage=3600 | The edge treats its copy as fresh for 1 hour. |
| Kamee's CDN | stale-while-revalidate=86400 | For 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.
Recommended shape
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.