Next-gen open-source URL shortener and marketing platform.
Zly is a full-featured URL shortening platform with analytics, custom domains, QR codes, link in bio pages, A/B testing, team collaboration, email campaigns, an admin panel, and a developer API, all self hosted.
| Category | Capabilities |
|---|---|
| Short Links | Custom slugs, password protection, scheduling (activate/expire), bulk import/export |
| Analytics | Per-link and workspace dashboards; clicks over time, browser/OS/device, top referrers; Chart.js visualizations |
| QR Codes | Auto-generated QR codes per link; PNG and SVG download; customizable colors |
| Link in Bio | Profile pages with curated link collections; midnight/dark/light themes |
| A/B Testing | Weighted destination variants with randomized traffic splitting; variant-level analytics |
| Custom Domains | DNS TXT verification; workspace-scoped custom domains; CNAME-ready |
| Email Campaigns | Contact management, email templates, campaign sending with open/click tracking |
| Teams | Multi-user workspaces; owner/member roles; invite flow with accept/decline |
| Admin Panel | User management, system stats, audit log with color-coded events |
| API | Full REST API with auto-generated OpenAPI docs; scoped API keys per workspace |
| Security | bcrypt password hashing; JWT auth with refresh tokens; rate limiting; CSRF protection; security headers (CSP, HSTS) |
| Component | Technology |
|---|---|
| Framework | FastAPI (Python 3.12+) |
| Database | PostgreSQL via SQLAlchemy 2.0 (async) |
| Cache | Redis 7 (optional, graceful fallback) |
| Auth | bcrypt + PyJWT |
| Frontend | Jinja2 + HTMX + Tailwind CSS (CDN) + Chart.js |
| Queue | arq (background jobs) |
| aiosmtplib (async SMTP) | |
| Migrations | Alembic |
| Containers | Docker, Docker Compose |
| Reverse Proxy | Caddy (auto HTTPS) |
# Clone
git clone https://github.com/pythonplumber/zly.git
cd zly
# Install with dev dependencies
pip install -e ".[dev]"
# Start the dev server (auto-creates tables)
uvicorn app.main:app --reload
# Open http://localhost:8000/dashboardThat's it. The app defaults to SQLite and gracefully handles Redis being unavailable.
Zly is built for self hosting. A $5 to $10/month VPS with Docker Compose gives you full control, persistent storage, Redis caching, and auto-HTTPS via Caddy with no platform lock-in.
# SSH into your VPS (Ubuntu 24+)
ssh root@your-server
# Clone and deploy
git clone https://github.com/pythonplumber/zly.git
cd zly
cp infrastructure/.env.example .env
# Edit .env with secure secrets
# Start everything
docker compose -f infrastructure/docker-compose.yml up -d
# Run migrations
docker compose exec fastapi alembic upgrade headFull guide in docs/DEPLOYMENT.md.
All configuration lives in app/config.py and is driven by environment variables:
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
sqlite+aiosqlite:///./zly.db |
Database connection string |
REDIS_URL |
redis://localhost:6379/0 |
Redis connection string (optional, graceful fallback) |
SECRET_KEY |
change-me-in-production |
General purpose secret |
JWT_SECRET |
change-me-in-production |
JWT signing key (min 32 bytes) |
JWT_ALGORITHM |
HS256 |
JWT signing algorithm |
ACCESS_TOKEN_EXPIRE_MINUTES |
15 |
JWT token lifetime (minutes) |
CORS_ORIGINS |
http://localhost:8000 |
Comma-separated allowed origins |
DEFAULT_DOMAIN |
localhost:8000 |
Base domain for short URLs |
Interactive OpenAPI docs at /docs (auto-generated by FastAPI). All routes prefixed /api/v1.
POST /api/v1/auth/register: Register a new userPOST /api/v1/auth/login: Login, receive JWT access + refresh tokensPOST /api/v1/auth/refresh: Refresh JWT tokenPOST /api/v1/auth/forgot-password: Request password reset emailPOST /api/v1/auth/reset-password: Reset password with tokenGET /api/v1/auth/oauth/{provider}: OAuth login (Google, GitHub)
GET /api/v1/users/me: Get current userPATCH /api/v1/users/me: Update profilePOST /api/v1/users/me/change-password: Change passwordPOST /api/v1/users/me/set-password: Set password (OAuth users)
POST /api/v1/links: Create a short linkGET /api/v1/links?workspace_id=: List links for a workspaceGET /api/v1/links/{id}: Get link detailsPATCH /api/v1/links/{id}: Update a linkDELETE /api/v1/links/{id}: Delete a linkPOST /api/v1/links/{id}/qrcode: Generate QR code (PNG or SVG)POST /api/v1/links/{id}/verify-password: Verify link password
GET /api/v1/links/{id}/analytics: Per-link analyticsGET /api/v1/workspaces/{id}/analytics/summary: Workspace-level summary
POST /api/v1/links/{id}/variants: Add an A/B variantGET /api/v1/links/{id}/variants: List variantsPATCH /api/v1/variants/{id}: Update a variantDELETE /api/v1/variants/{id}: Delete a variant
GET/POST /api/v1/workspaces: List/create workspacesGET/PUT/DELETE /api/v1/workspaces/{id}: Workspace detail/update/deletePOST /api/v1/workspaces/{id}/invites: Invite a memberGET /api/v1/workspaces/{id}/invites: List invitesGET /api/v1/workspaces/{id}/members: List members
GET/POST /api/v1/workspaces/{id}/email/contacts: Manage contactsGET/POST /api/v1/workspaces/{id}/email/templates: Manage templatesGET/POST /api/v1/workspaces/{id}/email/campaigns: Manage campaignsPOST /api/v1/workspaces/{id}/email/campaigns/{id}/send: Send campaignGET /api/v1/workspaces/{id}/email/campaigns/{id}/stats: Campaign stats
POST /api/v1/workspaces/{id}/bio: Create bio pageGET /api/v1/workspaces/{id}/bio: Get bio pagePUT /api/v1/workspaces/{id}/bio: Update bio page
POST /api/v1/workspaces/{id}/domains: Add a custom domainGET /api/v1/workspaces/{id}/domains: List domainsPOST /api/v1/workspaces/{id}/domains/{did}/verify: Verify domain (TXT record)DELETE /api/v1/workspaces/{id}/domains/{did}: Remove a domain
POST /api/v1/workspaces/{id}/api-keys: Create API keyGET /api/v1/workspaces/{id}/api-keys: List API keysDELETE /api/v1/api-keys/{id}: Delete API key
GET /api/v1/admin/users: List users (paginated)GET /api/v1/admin/users/{id}: Get user detailsPATCH /api/v1/admin/users/{id}: Update user (superuser, active)GET /api/v1/admin/stats: System statisticsGET /api/v1/admin/audit-logs: Audit log (paginated, filterable)
GET /api/v1/sessions: List active sessionsDELETE /api/v1/sessions/{jti}: Revoke a sessionPOST /api/v1/sessions/revoke-all: Revoke all sessions
GET /{short_code}: Redirect to destination URL
zly/
app/ FastAPI application
api/ Route handlers
core/ Config, security, dependencies, rate limiter
models/ SQLAlchemy ORM models
schemas/ Pydantic schemas
services/ Business logic
templates/ Jinja2 HTML templates (auth, dashboard, bio, errors, partials)
routes/ Dashboard and auth HTML page routes
db.py Database engine
config.py Settings
cli.py CLI management commands
main.py App factory
api/index.py Vercel serverless entrypoint
infrastructure/ Docker, Caddy, env config
migrations/ Alembic migrations
tests/ Pytest suite (210+)
worker/ arq background worker
docs/ Deployment and architecture guides
.github/ CI workflow
pyproject.toml Project config
See docs/ARCHITECTURE.md for detailed architecture documentation covering:
- Data model relationships and migration strategy
- Routing flow (redirect engine, caching, analytics recording)
- Authentication and authorization model
- Async job patterns and Redis integration
- Multi-tenancy via workspaces and teams
- Rate limiting and security middleware
pytest -v # 210+ tests covering all features
pytest --cov=app # With coverageruff check . # Linting (E, F, I, N, W, UP)
mypy app/ # Strict type checking# Create a new migration
alembic revision --autogenerate -m "description"
# Apply migrations
alembic upgrade head
# Rollback
alembic downgrade -1MIT, see LICENSE.