Caching & CDN
How szum's CDN serves cached charts instantly – and why audience-facing loads are free.
szum's GET endpoints are CDN-cached. The reason that matters is latency: a cache hit returns in tens of milliseconds without waiting on origin render, font loading, or theme application. For a chart in an article that gets 10,000 page views, origin render runs once per region per cache cycle (~20–40 times a day); every other load comes back from the edge instantly.
Cost-wise, only renders are billed, and only on cache misses. A CDN cache hit on /c/{id} or /e/{id} is served entirely from the edge, never reaches szum, and costs nothing – there is no separate per-view charge.
What each cache hit costs your plan
| Endpoint | Cache hit | Cache miss |
|---|---|---|
GET /c/{id} (saved image) | free | +1 render |
GET /e/{id} (interactive embed) | free | +1 render |
Example. A chart embedded in an article seen 2M times in a month: the CDN serves nearly all of those loads for free. The origin only re-renders ~20–40 times per region per day per cache cycle – a few thousand renders total, typically well within a Pro plan's included allotment. Your cost stays flat no matter how large the audience grows, because audience-facing loads are absorbed by the CDN.
How a render gets charged
Two things have to happen for one render to be charged:
- The request reaches origin (cache miss, cache expiry, or
Cache-Control: no-cachefrom the client). - The origin actually produces an image (passes validation, isn't rate-limited, isn't refused for plan reasons).
If either fails, the bucket isn't incremented. On render error after a successful origin hit, the bucket is refunded.
Cache headers per endpoint
| Endpoint | Cache-Control | Notes |
|---|---|---|
POST /chart | private, no-store | Authenticated; response carries X-Usage-* headers, must not be reused across keys |
GET /chart | public, max-age=86400, s-maxage=604800, stale-while-revalidate=86400 | 1d browser, 7d CDN, 1d SWR |
GET /c/{id} (saved image) | public, max-age=86400, s-maxage=86400, stale-while-revalidate=86400 | 1d browser, 1d CDN, 1d SWR – long TTL absorbs popularity bursts; staleness after an unpublish/delete is bounded to ≤1d (no immutable) |
GET /e/{id} (interactive embed) | public, max-age=86400, s-maxage=86400, stale-while-revalidate=86400 | Same as /c/{id} – HTML shell, identical kill-switch semantics |
GET /r/{id} (transient, MCP) | public, max-age=3600, immutable | 1h CDN cache, never revalidates – the id is single-use (10 min TTL); a rendered image harmlessly outlives its config |
Error responses on cacheable routes (404, 403, 429, 500) are returned with private, no-store, so a transient failure can't poison the edge cache.
Cache keys
The CDN keys on the full URL including query string. That means:
https://szum.io/chart?config={...}– two clients sending the same byte-identical config share the same cache entry. Reorder a JSON key and you get a fresh render. The query string IS the input here, so unique query strings produce different renders, which is intentional.https://szum.io/c/abc123andhttps://szum.io/e/abc123– stable; everyone who fetches the same id hits the same cache entry.
Use /c/{id} (saved charts) or /e/{id} (interactive embeds) when many recipients will fetch the same chart – emails, dashboards, Slack, iframes. The opaque short URL is bytes-identical for every recipient, so cache hit rates approach 100% and the average viewer gets the chart back instantly from the nearest edge.
See also
- API → Response – full header reference for
POST /chart - Saved charts → Rate limits – how
GET /c/{id}interacts with the creator's plan - Plans & Limits – what counts toward the monthly bucket