The Resident Council short-link tooling (stats, QR generation, and link creation) runs as
Cloudflare Pages Functions, replacing three n8n workflows that shared one Short.io
API key and one Notion Short.io DB. Shared plumbing lives in
functions/_lib/shortio.ts (Notion pagination, Short.io API calls, QR URL
builder) so each handler stays small.
Pieces
flowchart TD CRON["ihnyc-rc-cron-sync<br/>*/30"] --> STATS["/api/cron/sync-stats<br/>(X-Sync-Secret)"] ADMIN["admin / manual"] --> CREATE["/api/shortio/create<br/>(X-Sync-Secret)"] PUBLIC["link QR requests"] --> QR["/api/shortio/qr<br/>(public proxy)"] STATS --> LIB["_lib/shortio.ts"] CREATE --> LIB LIB --> SHORTIO["Short.io API"] LIB --> NOTION["Notion Short.io DB"] STATS --> R2["R2: stats.json (STATS_BUCKET)"] QR --> SHORTIO
| Endpoint | Replaces (n8n) | Does |
|---|---|---|
POST /api/cron/sync-stats | RC-ShortIO-GenerateStats-JSON | Queries Notion for Active links, fetches 30-day Short.io click stats in parallel (Promise.allSettled — one bad link can’t abort the job), writes stats.json to R2. Cron */30 (n8n ran every minute, overkill for last-30 stats). |
GET /api/shortio/qr?id=&name= | RC-ShortIO-Proxy-QRGen | Public proxy to the Short.io QR API; returns PNG with a 1h cache |
POST /api/shortio/create | RC-ShortIO-Create (/webhook/qr-init) | Finds Notion rows with an Original URL but no Short.io ID, creates the link, writes Short URL / ID / QR / Status / timestamps back to Notion |
Auth
sync-statsandcreaterequire theX-Sync-Secretheader.qris a public proxy (returns an image only).
Config / go-live
| Name | Type | Purpose |
|---|---|---|
SHORTIO_API_KEY | secret (sk_...) | Short.io API access |
X-Sync-Secret | secret | Gates sync-stats + create |
Set SHORTIO_API_KEY via wrangler pages secret put before decommissioning
n8n, and rotate the old exposed key at the same time. The */30 cron is added in
the master cron Worker’s toml — redeploy that Worker to activate it.