Files
Jason Stedwell 48d3445b09 docs: document new features, ops env vars, and API surface; refresh roadmap
- README: feature map updated (redirect types, protected links, QR vector
  export, audience analytics, API keys, admin observability, operations);
  API reference covers new endpoints and params. Roadmap now lists
  self-serve accounts, security hardening (rate limiting/CSRF/SSRF guard),
  Stripe billing, custom domains, and remaining power features.
- INSTALL: new environment variables (WEBHOOK_URL, GEOIP_DB_PATH,
  GEO_API_FALLBACK, IP_ANONYMIZE, CLICK_RETENTION_DAYS, BACKUPS,
  BACKUP_DIR, BACKUP_KEEP) with GeoLite2 setup notes and the ip-api.com
  licensing caveat.
- api-docs page: API-key authentication, new link/analytics/QR params,
  aggregate analytics, admin overview/audit/keys/purge, unlock route.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 01:40:44 -05:00

145 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# QRknit — URL Shortener & QR Code Generator
A managed URL shortener with QR code generation, deep click analytics, and tag-based link organization.
- **Multi-user** — admin account seeded from env vars; admin can create/delete/manage accounts via the UI
- **Per-user isolation** — each user sees only their own links and tags; admin has its own isolated workspace separate from all other accounts
- **Organizations** — group users into orgs that share link/tag visibility and pool usage quotas under a shared plan
- **Plan tiers** — Admin / Free / Starter / Pro / Team with enforced limits on active links, monthly clicks, and analytics history window; org members inherit their org's plan
- **Scoped tags** — tags are owned by the user who creates them; org members share a tag namespace, solo users and admins each have their own isolated namespace
- **Deep analytics** — daily charts, referrer/device/browser/country breakdowns, hourly 7×24 heatmap, raw click CSV export
- **Private instance** — Pro and Team plans include a dedicated private deployment on your own infrastructure
---
## 🗺 Feature Map
### Shipped
| Feature area | What's included |
|---|---|
| **Short links** | Random & custom codes, link expiry, pinned links, one-click copy, auto-fetch page title, per-link redirect type (302 default / 301 opt-in) |
| **Protected links** | Per-link password protection with a branded unlock page; branded visitor pages for missing, expired, and quota-limited links |
| **QR codes** | QR generation for any link or URL, logo overlay, dot-shape presets, inline thumbnail, copy to clipboard, **SVG / PDF / PNG export**, selectable error-correction level (L/M/Q/H) |
| **Analytics** | Per-link daily click charts, unique-visitor counts, bot/crawler filtering, referrer / device / browser / country breakdowns, hourly 7×24 heatmap, raw click CSV export |
| **Audience analytics** | Aggregate dashboard across the whole account or org, filterable by tag — one chart for a whole campaign |
| **Tags** | Tag links for filtering; tags are scoped per user — org members share a namespace, solo users and admins each have their own |
| **Bulk tools** | Bulk delete, bulk tag, bulk expire; CSV import & export |
| **Auth** | Session-cookie login, 30-day HttpOnly cookie, **API keys** (`Authorization: Bearer qrk_…`) with read/write scopes, works behind Cloudflare and Nginx Proxy Manager |
| **Multi-user** | Per-user link isolation, admin user-management panel, username + password login |
| **Organizations** | Group users into orgs that share link/tag visibility and pool usage quotas under a shared plan |
| **Plan tiers** | Free / Starter / Pro / Team plans with enforced limits on active links, monthly clicks, and analytics history; org plan overrides member plans |
| **Admin tier** | Admin accounts sit above all plans with unlimited quotas and an isolated workspace; can filter into any user's links on demand |
| **Admin observability** | Platform overview (totals, 30-day series, top links), per-user/org quota usage with near-limit warnings, audit log of admin actions, webhook notifications for contact messages |
| **Landing & portal** | Marketing landing page, SaaS pricing page, contact form, admin inbox with unread badge |
| **Operations** | Async click logging (redirects never block), local GeoLite2 geolocation, daily SQLite backups (`VACUUM INTO`), click-data retention & IP anonymisation options, hard-purge of deleted links, multi-worker-safe uptime tracking |
| **Deployment** | `APP_NAME` / `BASE_URL` env vars, four colour themes, single Docker container, Unraid-ready |
### Roadmap
| Feature area | What's planned |
|---|---|
| **Self-serve accounts** | Public signup with email verification, change-own-password, account page with plan & usage |
| **Security hardening** | Rate limiting (login, contact, QR endpoints), CSRF tokens, SSRF guard on title fetching |
| **Billing** | Stripe checkout & subscriptions, upgrade/downgrade UI, self-serve account portal |
| **Custom domains** | CNAME-based short domains, SSL provisioning, domain verification, Team tier multi-domain support |
| **Power features** | UTM parameter builder, dead-link detection, folders/groups, duplicate link |
---
> **Installing?** See [INSTALL.md](INSTALL.md) for Docker, Docker Compose, and Unraid setup instructions, environment variables, and update steps.
---
## 🔌 API Reference
Authenticated endpoints accept either an active session (log in via the web UI or `POST /api/auth/login`) **or an API key**: `Authorization: Bearer qrk_…`. Keys are created in the dashboard (or via `POST /api/keys`) with a `read` or `write` scope — read-only keys can only call GET endpoints.
### Auth & API keys
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | `/api/auth/login` | — | Login — `{"username": "...", "password": "..."}`, sets session cookie; returns `plan`, `plan_limits`, `org_id` |
| POST | `/api/auth/logout` | ✓ | Clear session |
| GET | `/api/auth/me` | ✓ | Returns `{"authenticated": true, "username": "...", "is_admin": bool, "org_id": int\|null, "plan": "...", "plan_limits": {…}}` |
| GET | `/api/keys` | ✓ | List your API keys (prefix, scope, last used — never the full token) |
| POST | `/api/keys` | ✓ | Create key — `{name, scope: "read"\|"write"}`; response includes `token` **once** |
| DELETE | `/api/keys/:id` | ✓ | Revoke a key (admins can revoke any key) |
### Links
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | `/api/shorten` | ✓ | Create a short link — `{url, custom_code?, title?, expires_at?, tags?, redirect_type?: 301\|302, password?}` |
| GET | `/api/links` | ✓ | List links — supports `?q=`, `?tag=`, `?page=`, `?per_page=`; org members see all links within their org; admin sees own links only, optionally filtered with `?user=<username>` |
| GET | `/api/links/:code` | ✓ | Link detail — includes `created_by`, `redirect_type`, `has_password` |
| PATCH | `/api/links/:code` | ✓ | Edit link — `url`, `title`, `expires_at`, `tags`, `is_pinned`, `redirect_type` (301/302), `password` (empty string removes protection) |
| DELETE | `/api/links/:code` | ✓ | Delete link (soft — recoverable until purged by an admin) |
| GET | `/api/links/:code/analytics` | ✓ | Click analytics — supports `?days=7\|30\|90` (capped to plan's max window) and `?exclude_bots=1`; returns `daily` (with per-day `unique`), `unique_visitors`, `bot_clicks`, `referrers`, `devices`, `browsers`, `countries`, `heatmap` (7×24 array), and `max_days` |
| GET | `/api/analytics/aggregate` | ✓ | Account/org-wide analytics — `?days=`, `?tag=`, `?exclude_bots=1`; returns `daily`, `unique_visitors`, `referrers`, `devices`, `browsers`, `countries`, `top_links` |
| GET | `/api/links/:code/clicks/export` | ✓ | Download raw click events as CSV — columns: `timestamp`, `referrer`, `device`, `browser`, `country` |
### Utilities
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | `/api/stats` | ✓ | Total links, total clicks, clicks/7d, top 5 links, and a 30-day `daily` click array for the dashboard chart |
| GET | `/api/plan` | ✓ | Current user's effective plan, limits, and live usage — `{plan, limits: {max_links, monthly_clicks, analytics_days}, usage: {active_links, monthly_clicks}}`; org members see pooled usage across all org members; admin plan is always `"admin"` (unlimited) |
| GET | `/api/tags` | ✓ | Tags scoped to the requesting user — own tags only for solo users and admins; all org member tags for org members |
| GET | `/api/fetch-title` | ✓ | Server-side page title fetch — `?url=`. Returns `{"title":"…"}` |
| GET | `/api/qr/:code` | — | QR for a short link — `?format=png\|svg\|pdf`, `?ec=L\|M\|Q\|H`, `?fg=`, `?bg=`, `?size=`, `?style=` (raster only), `?download=1` |
| GET | `/api/qr/custom` | — | QR for any URL — same params as above plus `?url=` |
| POST | `/api/qr/custom` | — | QR with logo overlay — `{url, fg, bg, size, style, ec, format, logo}` (logo as base64; forces error correction H) |
| POST | `/api/links/bulk` | ✓ | Bulk operations — `{action: "delete"\|"tag"\|"expire", codes: […]}` |
| GET | `/api/links/export` | ✓ | Download all links as CSV |
| POST | `/api/links/import` | ✓ | Import links from CSV — `{csv: "…"}` |
| GET | `/api/health` | — | Health check — `{"status":"ok"}` |
| GET | `/:code` | — | Redirect to destination URL (302 by default; 301 if configured per link; password-protected links show a branded unlock page) |
| POST | `/:code/unlock` | — | Password submission for protected links (form field `password`) |
### Admin
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | `/api/admin/users` | Admin | List all users with link counts, plan, org name, and org plan |
| POST | `/api/admin/users` | Admin | Create user — `{username, password, is_admin, plan}` |
| PATCH | `/api/admin/users/:id` | Admin | Update plan, admin status, and/or org — `{plan?, is_admin?, org_id?}` (set `org_id: null` to remove from org) |
| DELETE | `/api/admin/users/:id` | Admin | Delete user (cannot delete self) |
| PATCH | `/api/admin/users/:id/password` | Admin | Change user password — `{password}` |
| GET | `/api/admin/organizations` | Admin | List all organizations with member counts and plan |
| POST | `/api/admin/organizations` | Admin | Create organization — `{name, plan}` |
| PATCH | `/api/admin/organizations/:id` | Admin | Update organization name and/or plan — `{name?, plan?}` |
| DELETE | `/api/admin/organizations/:id` | Admin | Delete organization — members are unassigned but not deleted |
| GET | `/api/admin/messages` | Admin | List all contact/portal messages, newest first |
| DELETE | `/api/admin/messages/:id` | Admin | Delete a message |
| PATCH | `/api/admin/messages/:id/read` | Admin | Mark a message as read |
| GET | `/api/admin/overview` | Admin | Platform totals, 30-day click series, top links, per-user quota usage with near-limit flags |
| GET | `/api/admin/audit` | Admin | Audit log of admin actions — `?limit=` (default 200) |
| GET | `/api/admin/keys` | Admin | List all API keys across users (prefix, scope, last used) |
| POST | `/api/admin/purge` | Admin | Hard-delete soft-deleted links older than `{days}` — frees short codes, removes click history |
### Contact
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | `/api/contact` | — | Submit a contact message — `{name, email, subject, body}` |
---
## Third-party attributions
QRknit is built on open-source components. See [NOTICES.md](NOTICES.md) for full licence texts.
| Library | Licence | Used for |
|---|---|---|
| [Flask](https://flask.palletsprojects.com) | BSD-3-Clause | Web framework & routing |
| [Werkzeug](https://werkzeug.palletsprojects.com) | BSD-3-Clause | Password hashing, WSGI utilities |
| [qrcode](https://github.com/lincolnloop/python-qrcode) | MIT | QR code generation |
| [Pillow](https://python-pillow.org) | HPND | Image processing & QR rendering |
| [Gunicorn](https://gunicorn.org) | MIT | Production WSGI server |
| [Google Fonts](https://fonts.google.com) — Orbitron, Barlow, Share Tech Mono | OFL-1.1 | UI typography |
---
© 2025 QRknit. All rights reserved.