- Per-link analytics gain unique-visitor counts (period total and per-day),
a bot_clicks figure, and an exclude_bots=1 filter applied across every
breakdown, the daily series, and the heatmap. Bot classification comes
from the is_bot flag stamped at click time.
- New GET /api/analytics/aggregate: one chart across the whole account (or
org pool), optionally scoped to a single tag — daily clicks + uniques,
referrers, devices, browsers, countries, and top links, capped to the
plan's analytics window.
- Dashboard: Audience panel with tag/window/bot controls wired into
loadDashboard, plus a NO BOTS toggle and PERIOD/UNIQUE/BOTS stat line on
each link's analytics.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Self-serve keys: GET/POST /api/keys, DELETE /api/keys/:id (admins may
revoke any key), GET /api/admin/keys for a platform-wide view. Tokens are
qrk_-prefixed, stored only as a SHA-256 hash, and returned exactly once at
creation. Read-scope keys are rejected with 403 on non-GET requests; key
creation and revocation are recorded in the audit log.
Dashboard gets a self-serve key panel with copy-once token display, scope
badges, and last-used timestamps.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All QR endpoints (/api/qr/:code, /api/qr/custom GET+POST) accept
format=png|svg|pdf, ec=L|M|Q|H, and download=1 for attachment responses,
unified behind a shared qr_response() renderer. SVG is generated as a true
vector (one path of modules, crispEdges) with an optional logo embedded as
a data URI over a cleared centre tile; PDF wraps the raster render for
print. A logo always forces error correction H. Dot styles remain
raster-only.
QR Studio: format and error-correction selectors, PDF previews as PNG,
format-aware download button with contextual hints.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Redirects default to 302 so browsers stop permanently caching the hop —
click analytics stay accurate and destination edits take effect for
returning visitors. 301 is a per-link opt-in (create/edit/API).
- Links can carry a password: visitors get a branded unlock page at /:code
and POST to /:code/unlock. Unlocks always redirect with 302.
- Missing, expired, and quota-limited links now show branded standalone
visitor pages (404/410/429) instead of bouncing to the marketing site
with ?error= query params.
- Clicks are recorded through the async pipeline with bot/crawler tagging
(UA heuristics), GeoLite2 country lookup (ip-api fallback gated behind
GEO_API_FALLBACK), and the IP_ANONYMIZE policy applied at write time.
- Deletes stamp deleted_at for later hard-purge.
- Dashboard: redirect-type selector and password set/replace/remove in the
edit form, lock badge on protected links.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>