Full-featured REST API for OWASP BLT running on Cloudflare Workers
Features • Quick Start • API Endpoints • Development • Deployment
BLT-API is a high-performance, edge-deployed REST API that interfaces with all aspects of the OWASP BLT (Bug Logging Tool) project. Built using Python on Cloudflare Workers, it provides efficient, globally-distributed access to BLT's bug bounty platform.
- 🚀 Edge-deployed - Runs on Cloudflare's global network for low latency
- 🐍 Python-powered - Built with Python for Cloudflare Workers
- �️ D1 Database - Uses Cloudflare D1 (SQLite) for data persistence
- �🔒 Secure - CORS enabled, authentication support
- 📊 Full API Coverage - Access to bugs, users, domains, organizations, projects, hunts, and more
- 📖 Well-documented - Comprehensive API documentation
- ⚡ Fast - Optimized for quick cold starts and efficient execution
# Clone the repository
git clone https://github.com/OWASP-BLT/BLT-API.git
cd BLT-API
# Install dependencies
uv sync
# Install workers-py
uv tool install workers-py# Setup local database (automated script)
bash scripts/setup_local_db.sh
# Or manually:
wrangler d1 migrations apply blt-api --local
wrangler d1 execute blt-api --local --file=test_data.sql
# Start the development server
wrangler dev --port 8787
# The API will be available at http://localhost:8787For detailed setup instructions, see CONTRIBUTING.md.
# Install test dependencies
uv sync --extra dev
# Run unit tests
uv run pytest
# Run specific test file
uv run pytest tests/test_router.py -vNote: Integration tests for bugs endpoints are in development. You can test endpoints manually with the dev server running at http://localhost:8788.
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
API homepage with interactive documentation |
| GET | /health |
Health check endpoint |
| Method | Endpoint | Description |
|---|---|---|
| POST | /auth/signup |
Register a new user |
| POST | /auth/signin |
Sign in and get auth token |
| GET | /auth/verify-email |
Verify email address (link from email) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /bugs |
List all bugs (paginated) |
| GET | /bugs/{id} |
Get a specific bug with screenshots and tags |
| POST | /bugs |
Create a new bug |
| GET | /bugs/search?q={query} |
Search bugs by URL or description |
Query Parameters:
page- Page number (default: 1)per_page- Items per page (default: 20, max: 100)status- Filter by status (e.g.,open,closed,reviewing)domain- Filter by domain IDverified- Filter by verification status (trueorfalse)
Example Request:
curl "http://localhost:8787/bugs?page=1&per_page=10&status=open&verified=true"Example Response:
{
"success": true,
"data": [
{
"id": 1,
"url": "https://example.com/page",
"description": "SQL injection vulnerability",
"status": "open",
"verified": 1,
"score": 85,
"views": 125,
"created": "2026-02-17 10:30:00",
"modified": "2026-02-17 10:30:00",
"is_hidden": 0,
"rewarded": 50,
"cve_id": "CVE-2024-12345",
"cve_score": 8.5,
"domain": 1,
"domain_name": "Example Corp",
"domain_url": "https://example.com"
}
],
"pagination": {
"page": 1,
"per_page": 10,
"count": 10,
"total": 150,
"total_pages": 15
}
}Returns detailed bug information including screenshots and tags.
Example Request:
curl "http://localhost:8787/bugs/5"Example Response:
{
"success": true,
"data": {
"id": 5,
"url": "https://example.com/admin",
"description": "Authentication bypass vulnerability",
"markdown_description": "# Detailed Description\n\nFound auth bypass...",
"label": "critical",
"views": 245,
"verified": 1,
"score": 95,
"status": "open",
"user_agent": "Mozilla/5.0...",
"ocr": null,
"screenshot": "https://cdn.example.com/screenshot.png",
"closed_date": null,
"github_url": "https://github.com/example/repo/issues/123",
"created": "2026-02-17 08:15:00",
"modified": "2026-02-17 10:30:00",
"is_hidden": 0,
"rewarded": 100,
"reporter_ip_address": null,
"cve_id": "CVE-2024-67890",
"cve_score": 9.1,
"hunt": 3,
"domain": 1,
"user": 42,
"closed_by": null,
"domain_id": 1,
"domain_name": "Example Corp",
"domain_url": "https://example.com",
"domain_logo": "https://cdn.example.com/logo.png",
"screenshots": [
{
"id": 1,
"image": "https://cdn.example.com/screenshot1.png",
"created": "2026-02-17 08:20:00"
},
{
"id": 2,
"image": "https://cdn.example.com/screenshot2.png",
"created": "2026-02-17 08:25:00"
}
],
"tags": [
{"id": 1, "name": "authentication"},
{"id": 2, "name": "critical"},
{"id": 3, "name": "web"}
]
}
}Search for bugs by URL or description text.
Query Parameters:
q- Search query (required)limit- Maximum results to return (default: 10, max: 100)
Example Request:
curl "http://localhost:8787/bugs/search?q=sql+injection&limit=20"Example Response:
{
"success": true,
"query": "sql injection",
"data": [
{
"id": 15,
"url": "https://example.com/search",
"description": "SQL injection in search parameter",
"status": "open",
"verified": 1,
"score": 80,
"views": 89,
"created": "2026-02-16 14:30:00",
"modified": "2026-02-16 14:30:00",
"is_hidden": 0,
"rewarded": 0,
"cve_id": null,
"cve_score": null,
"domain": 2,
"domain_name": "Test Site",
"domain_url": "https://test.example.com"
}
]
}Create a new bug report.
Required Fields:
url- URL where the bug was found (max 200 characters)description- Brief description of the bug
Optional Fields:
markdown_description- Detailed markdown descriptionlabel- Bug label/categoryviews- View countverified- Verification status (boolean)score- Score/severity (integer)status- Status (default: "open")user_agent- User agent stringocr- OCR textscreenshot- Screenshot URLgithub_url- Related GitHub issue URLis_hidden- Hidden status (boolean)rewarded- Reward amount (integer, default: 0)reporter_ip_address- Reporter IPcve_id- CVE identifiercve_score- CVE scorehunt- Hunt IDdomain- Domain IDuser- User IDclosed_by- User ID who closed
Example Request:
curl -X POST "http://localhost:8787/bugs" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/vulnerable-page",
"description": "XSS vulnerability in comment field",
"markdown_description": "# XSS Vulnerability\n\nFound reflected XSS...",
"status": "open",
"verified": true,
"score": 75,
"domain": 1,
"user": 42
}'Example Response:
{
"success": true,
"message": "Bug created successfully",
"data": {
"id": 156,
"url": "https://example.com/vulnerable-page",
"description": "XSS vulnerability in comment field",
"status": "open",
"verified": 1,
"score": 75,
"domain": 1,
"user": 42,
"created": "2026-02-18 09:15:00",
"modified": "2026-02-18 09:15:00"
}
}Bugs endpoints use Cloudflare D1 database for direct queries. See docs/DATABASE.md for schema details.
| Method | Endpoint | Description |
|---|---|---|
| GET | /users |
List all users (paginated) |
| GET | /users/{id} |
Get a specific user |
| GET | /users/{id}/profile |
Get user profile with statistics |
| GET | /users/{id}/bugs |
Get bugs reported by user |
| GET | /users/{id}/domains |
Get domains submitted by user |
| GET | /users/{id}/followers |
Get users following this user |
| GET | /users/{id}/following |
Get users this user follows |
| Method | Endpoint | Description |
|---|---|---|
| GET | /domains |
List all domains (paginated) |
| GET | /domains/{id} |
Get a specific domain |
| GET | /domains/{id}/tags |
Get tags for a domain |
Domain endpoints use Cloudflare D1 database. See docs/DATABASE.md for details.
| Method | Endpoint | Description |
|---|---|---|
| GET | /organizations |
List all organizations (paginated) |
| GET | /organizations/{id} |
Get a specific organization |
| GET | /organizations/{id}/repos |
Get organization repositories |
| GET | /organizations/{id}/projects |
Get organization projects |
| Method | Endpoint | Description |
|---|---|---|
| GET | /projects |
List all projects (paginated) |
| GET | /projects/{id} |
Get a specific project |
| GET | /projects/{id}/contributors |
Get project contributors |
| Method | Endpoint | Description |
|---|---|---|
| GET | /hunts |
List all bug hunts |
| GET | /hunts/{id} |
Get a specific hunt |
| GET | /hunts/active |
Get currently active hunts |
| GET | /hunts/previous |
Get past hunts |
| GET | /hunts/upcoming |
Get upcoming hunts |
| Method | Endpoint | Description |
|---|---|---|
| GET | /stats |
Get platform statistics |
| Method | Endpoint | Description |
|---|---|---|
| GET | /leaderboard |
Get global leaderboard |
| GET | /leaderboard/monthly |
Get monthly leaderboard |
| GET | /leaderboard/organizations |
Get organization leaderboard |
Query Parameters for /leaderboard/monthly:
month- Month number (1-12)year- Year (e.g., 2024)
| Method | Endpoint | Description |
|---|---|---|
| GET | /contributors |
List all contributors |
| GET | /contributors/{id} |
Get a specific contributor |
| Method | Endpoint | Description |
|---|---|---|
| GET | /repos |
List repositories |
| GET | /repos/{id} |
Get a specific repository |
All API responses follow a consistent JSON format:
{
"success": true,
"data": { ... },
"pagination": {
"page": 1,
"per_page": 20,
"count": 10,
"total": 100
}
}{
"error": true,
"message": "Error description",
"status": 400
}This project uses Cloudflare D1 (SQLite) for data persistence. Some endpoints query the D1 database directly, while others proxy to the BLT backend API.
/domains- Domain data stored in D1/domains/{id}/tags- Domain tags from D1/bugs- Bugs data stored in D1/bugs/{id}- Bug details with screenshots and tags from D1
# Setup local database (recommended - uses script)
bash scripts/setup_local_db.sh
# Setup remote database
bash scripts/setup_remote_db.sh
# Or manually:
# Apply migrations locally
wrangler d1 migrations apply blt-api --local
# Load test data
wrangler d1 execute blt-api --local --file=test_data.sql
# Create new migration
wrangler d1 migrations create blt-api <description>
# Apply to production
wrangler d1 migrations apply blt-api --remoteFor complete database guide including queries, schema, and patterns, see docs/DATABASE.md.
BLT-API/
├── src/
│ ├── __init__.py # Package initialization
│ ├── main.py # Worker entry point
│ ├── router.py # URL routing
│ ├── utils.py # Utility functions
│ ├── client.py # BLT backend HTTP client
│ ├── libs/ # Library modules
│ │ ├── __init__.py
│ │ ├── db.py # Database helpers
│ │ ├── constant.py # Constants and config
│ │ └── jwt_utils.py # JWT authentication utilities
│ ├── handlers/ # Request handlers
│ │ ├── __init__.py
│ │ ├── auth.py # Authentication (signup, signin, verify)
│ │ ├── bugs.py # Bugs endpoints
│ │ ├── users.py # Users endpoints
│ │ ├── domains.py # Domains (D1-integrated)
│ │ ├── organizations.py
│ │ ├── projects.py
│ │ ├── hunts.py
│ │ ├── stats.py
│ │ ├── leaderboard.py
│ │ ├── contributors.py
│ │ ├── repos.py
│ │ ├── health.py
│ │ └── homepage.py # Interactive API documentation
│ ├── services/ # Service modules
│ │ ├── __init__.py
│ │ ├── email_service.py # Email sending (Mailgun)
│ │ ├── email_templates.py # Email template renderer
│ │ └── templates/ # Email HTML templates
│ │ ├── base.html # Base email template
│ │ ├── welcome.html # Welcome email
│ │ ├── verification.html # Email verification
│ │ ├── password_reset.html
│ │ └── bug_confirmation.html
│ └── pages/ # Static pages
│ └── index.html # API homepage template
├── scripts/ # Utility scripts
│ ├── migrate.sh # Auto-migration script for deployments
│ ├── setup_local_db.sh # Local database setup
│ └── setup_remote_db.sh # Remote database setup
├── migrations/ # D1 database migrations
│ ├── 0001_init.sql
│ ├── 0002_add_bugs.sql
│ └── 0003_user_schema.sql
├── docs/ # Documentation
│ └── DATABASE.md # D1 database guide
├── tests/ # Test files
├── test_data.sql # Sample data for development
├── wrangler.toml # Cloudflare Workers config
├── pyproject.toml # Python project config
├── CONTRIBUTING.md # Contribution guide
└── README.md
- Create a new handler in
src/handlers/ - Import and export it in
src/handlers/__init__.py - Register the route in
src/main.py
Configure these in wrangler.toml:
| Variable | Description | Default |
|---|---|---|
BLT_API_BASE_URL |
BLT backend API URL | https://blt.owasp.org/api/v1 |
BLT_WEBSITE_URL |
BLT website URL | https://blt.owasp.org |
JWT_SECRET |
Secret key for JWT tokens | Required |
MAILGUN_API_KEY |
Mailgun API key (Private or Sending API key) | Required for email |
MAILGUN_DOMAIN |
Mailgun domain (sandbox or custom domain) | Required for email |
The API uses Mailgun for sending emails (verification, password reset, etc.). You have two options:
Option 1: Sandbox Domain (Testing Only)
- ✅ Free for testing
- ❌ Can only send to authorized recipients (max 5)
- ❌ Cannot send to real users
- Domain format:
sandbox123...mailgun.org
MAILGUN_DOMAIN = "sandbox120cc536878b42198d6b4f33b30e2877.mailgun.org"To authorize test recipients:
- Log in to Mailgun dashboard
- Go to Sending → Authorized Recipients
- Add your test email addresses
- Confirm via the email Mailgun sends
Option 2: Custom Domain (Production)
- ✅ Can send to anyone
- ✅ Professional sender address (e.g.,
noreply@yourdomain.com) - Requires domain verification (DNS setup)
- Domain format:
yourdomain.comormg.yourdomain.com
MAILGUN_DOMAIN = "post0.live" # or "mg.yourdomain.com"To set up a custom domain:
- Log in to Mailgun dashboard
- Go to Sending → Domains → Add New Domain
- Enter your domain (e.g.,
post0.live) - Add the DNS records (TXT, MX, CNAME) to your domain registrar
- Wait for verification (usually 15-30 minutes)
API Keys:
- Private API Key: Full access to all Mailgun operations (use for development)
- Sending API Key: Restricted to only sending messages (recommended for production)
Both keys work the same way. Generate Sending API Key in Mailgun dashboard → Settings → API Keys → Create New Sending Key
The project is configured to automatically run D1 migrations before every deployment using Wrangler's build command. The migrations are defined in wrangler.toml:
[build]
command = "bash scripts/migrate.sh"This means migrations will run automatically whenever you deploy, whether:
- Deploying manually with
wrangler deploy - Using Cloudflare's Git integration (automatic deploy on push)
- Running in CI/CD pipelines
# Login to Cloudflare (first time only)
wrangler login
# Deploy to production (migrations run automatically)
wrangler deploy
# Deploy to specific environment
wrangler deploy --env production
wrangler deploy --env developmentFor automatic deployments when code is pushed to your repository:
- Go to Cloudflare Dashboard → Workers & Pages
- Connect your GitHub/GitLab repository
- Configure build settings:
- Build command: Leave empty (build command is defined in
wrangler.toml) - Deploy command:
wrangler deploy
- Build command: Leave empty (build command is defined in
- Every push to your main branch will automatically:
- Run D1 migrations (via build command in
wrangler.toml) - Deploy the updated worker
- Run D1 migrations (via build command in
See Cloudflare Git Integration docs for details.
If you need to run migrations separately:
# Apply migrations only
wrangler d1 migrations apply blt-api --remote
# Deploy without running build command (⚠️ WARNING: skips migrations!)
wrangler deploy --no-buildNote: Using --no-build will skip the migration step, which could lead to deploying code that expects schema changes that haven't been applied. Only use this if you've already run migrations separately.
Some endpoints require authentication. Pass the auth token in the request header:
curl -H "Authorization: Token YOUR_API_TOKEN" https://your-worker.workers.dev/bugsThe API follows Cloudflare Workers' execution limits:
- CPU time: 50ms (free), 30s (paid)
- Memory: 128MB
- Request size: 100MB
We welcome contributions! Please see CONTRIBUTING.md for detailed setup instructions and development guidelines.
Quick start:
- Fork the repository
- Setup local environment (see CONTRIBUTING.md)
- Create a feature branch
- Make your changes
- Test locally with
wrangler dev - Submit a pull request
For database changes, see docs/DATABASE.md.
- OWASP BLT - Main BLT project
- BLT Website - Live BLT platform
This project is licensed under the AGPL-3.0 License - see the LICENSE file for details
- 💬 OWASP Slack - Join #project-blt
- 🐛 GitHub Issues - Report bugs
- 📖 BLT Documentation
Made with ❤️ by the OWASP BLT Community