Stats
A click is written twice, both times inside ctx.waitUntil, never on the
response path. A failed analytics write costs a click in the stats; it never
costs a redirect.
D1 stats_daily |
Analytics Engine | |
|---|---|---|
| Holds | clicks + bots, per link, per UTC day | country, device, OS, browser, in-app, referer host, decision branch |
| Retention | permanent | 3 rolling months |
| Latency | immediate | ~1 minute of ingestion |
| Read by | GET /api/stats |
Cloudflare SQL API, from a laptop |
The daily counter in D1 exists because Analytics Engine only keeps three months. Without it there would be no history at all.
Permanent counts
Section titled “Permanent counts”curl -s "https://301.so/api/stats?days=7" \ -H "Authorization: Bearer $TOKEN" -A "my-client/1.0"Shape and parameters: API reference. Readable the instant the click
lands, because it is a plain INSERT … ON CONFLICT DO UPDATE in SQLite.
Dimensions
Section titled “Dimensions”Analytics Engine is queried through Cloudflare’s SQL API with an account-scoped token:
ACC=<account id>curl -s "https://api.cloudflare.com/client/v4/accounts/$ACC/analytics_engine/sql" \ -H "Authorization: Bearer $CF_TOKEN" \ --data "SELECT blob2 AS slug, blob9 AS webview, blob11 AS outcome, SUM(_sample_interval) AS clicks FROM clicks_301so WHERE timestamp > NOW() - INTERVAL '7' DAY GROUP BY slug, webview, outcome ORDER BY clicks DESC FORMAT JSON"Column order is a contract — queries address columns by position, so new fields are only ever appended. Inserting one in the middle would rewrite the past.
| Column | Contents |
|---|---|
index1 |
link_id (sampling is per index) |
blob1…blob5 |
hostname, slug, team id, country, colo |
blob6…blob9 |
device, OS, browser, in-app browser |
blob10, blob11 |
referer hostname, decision branch |
double1 |
1 if bot |
Why the worker never reads Analytics Engine
Section titled “Why the worker never reads Analytics Engine”Reading it needs an account-level Cloudflare token. Putting one inside a worker that serves public traffic would trade the entire Cloudflare account against a stats screen. So the read path stops at D1, and dimensions are read from Vincent’s machine — where the token already lives.