Base URL:
http://localhost:5000/api
Auth: All protected routes require aBearer <accessToken>in theAuthorizationheader (or theaccessTokenhttpOnly cookie). The access token is obtained at login and is valid for 15 minutes. Use/auth/refresh-tokento rotate.
- Authentication
- Users & Profiles
- Programs
- Reports
- Statistics & Analytics
- Certificates
- Admin
- WebSocket Events
- Error Codes
Create a new hunter account.
Body (JSON)
| Field | Type | Required |
|---|---|---|
fullName |
string | ✅ |
email |
string | ✅ |
password |
string ≥ 8 chars | ✅ |
collegeName |
string | – |
role |
HUNTER | PROGRAM_OWNER |
– (default HUNTER) |
Response 201
{
"success": true,
"message": "Registration successful. Please verify your email.",
"data": { "userId": "..." }
}Authenticate and receive JWT tokens.
Body (JSON)
| Field | Type | Required |
|---|---|---|
email |
string | ✅ |
password |
string | ✅ |
totpCode |
string (6-digit) | if 2FA is enabled |
Response 200 — sets accessToken and refreshToken as httpOnly cookies.
{
"success": true,
"data": {
"user": { "_id": "...", "fullName": "...", "role": "HUNTER", "level": "TRACKER" },
"accessToken": "eyJ..."
}
}Lockout: 5 consecutive failed logins locks the account for 15 minutes.
2FA redirect: iftwoFactorEnabled=trueandtotpCodeis missing, returns403 TOTP_REQUIRED.
🔒 Protected. Clears refresh token cookie and invalidates server-side token.
Rotate the access token using the refreshToken cookie.
Response 200
{ "success": true, "data": { "accessToken": "eyJ..." } }Verify email address from the token sent during registration.
Send a password-reset email.
Body: { "email": "..." }
Reset password using the emailed token.
Body: { "password": "newSecurePassword1!" }
🔒 Protected. Generate a TOTP secret and QR code for Google Authenticator.
Response 200
{
"success": true,
"data": {
"secret": "BASE32_SECRET",
"otpAuthUrl": "otpauth://totp/BugArena:user@example.com?secret=...",
"qrCodeDataUrl": "data:image/png;base64,..."
}
}🔒 Protected. Activate 2FA by submitting the first valid TOTP code.
Body: { "token": "123456" }
🔒 Protected. Deactivate 2FA.
Body: { "token": "123456" }
Public profile. If a valid Bearer token is present, returns isFollowing status.
Response 200
{
"success": true,
"data": {
"user": { "_id": "...", "fullName": "Alice Chen", "level": "TRACKER", "totalPoints": 750, "rank": 3 },
"badges": [{ "badgeId": { "name": "First Blood", "description": "..." }, "_id": "..." }],
"recentReports": [ { "_id": "...", "title": "...", "severity": "HIGH", "status": "ACCEPTED" } ],
"followerCount": 12,
"followingCount": 5,
"isFollowing": true
}
}🔒 Protected. Toggle follow/unfollow. Returns the new state.
Response 200
{ "success": true, "data": { "followed": true }, "message": "Followed user successfully" }🔒 Protected. Activity feed — recent notifications from hunters you follow (type: REPORT_ACCEPTED, BADGE_EARNED, POINTS_AWARDED). Populated with userId.fullName, userId.avatarUrl, userId.level.
Response 200
{
"success": true,
"data": {
"feed": [
{
"_id": "...",
"type": "REPORT_ACCEPTED",
"message": "✅ Your report \"XSS in PayPal\" was accepted!",
"userId": { "fullName": "Bob Ramirez", "avatarUrl": "", "level": "SCOUT" },
"createdAt": "2026-05-29T10:00:00.000Z"
}
]
}
}🔒 Protected. Update own profile fields.
Body (JSON — all optional)
{ "fullName": "...", "collegeName": "...", "bio": "...", "githubUrl": "...", "linkedinUrl": "...", "twitterUrl": "..." }🔒 Protected. Change password.
Body: { "currentPassword": "...", "newPassword": "..." }
🔒 Protected. Permanently delete own account.
🔒 Protected. All earned badges for the authenticated user.
🔒 Protected. All earned certificates.
🔒 Protected. Paginated notifications list.
Query params: limit (default 20), page (default 1)
🔒 Protected. Mark a single notification as read.
🔒 Protected. Mark all notifications as read.
List approved programs. Supports filtering and pagination.
Query params
| Param | Type | Description |
|---|---|---|
limit |
number | Default 20 |
page |
number | Default 1 |
category |
string | WEB, API, MOBILE, NETWORK |
difficulty |
string | EASY, MEDIUM, HARD |
sort |
string | reward, createdAt |
Response 200
{
"success": true,
"data": { "programs": [ {...} ], "total": 5, "page": 1, "pages": 1 }
}Get a single program by slug.
🔒 Protected (PROGRAM_OWNER or ADMIN). Create a new program.
Body (JSON)
{
"name": "PayPal Security",
"description": "...",
"category": "WEB",
"difficulty": "HARD",
"rewards": { "critical": 50000, "high": 25000, "medium": 10000, "low": 3000, "info": 500 },
"scopeUrls": ["*.paypal.com"],
"outOfScopeUrls": ["sandbox.paypal.com"],
"tags": ["fintech"]
}🔒 Protected (owner or admin). Update program details.
🔒 Protected (owner or admin). Soft-deactivate a program.
🔒 Protected. Hunters see their own reports; admins/owners see all or scoped to their program.
Query params: limit, page, status, severity, programId
🔒 Protected (HUNTER). Submit a new vulnerability report.
Content-Type: multipart/form-data
| Field | Type | Required |
|---|---|---|
title |
string | ✅ |
programId |
ObjectId | ✅ |
vulnerabilityType |
string | ✅ |
affectedUrl |
string | ✅ |
severity |
enum | ✅ |
cvssScore |
number 0-10 | ✅ |
stepsToReproduce |
markdown string | ✅ |
impactDescription |
markdown string | ✅ |
pocVideoUrl |
URL string | – |
isDraft |
boolean | – |
screenshots |
file(s) | – |
Duplicate warning: If the title/steps share ≥ 70% similarity with an existing report, the response includes { "duplicateWarning": true, "similarReports": [...] }.
Response 201
{ "success": true, "data": { "report": { "_id": "...", "status": "PENDING" } } }🔒 Protected. Get full report details.
🔒 Protected (PROGRAM_OWNER or ADMIN). Update report status.
State machine:
DRAFT → PENDING → TRIAGING → ACCEPTED → REWARDED
→ REJECTED
→ DUPLICATE
Body: { "status": "ACCEPTED", "note": "Confirmed. Reward processing." }
Returns
422if the transition is illegal.
🔒 Protected (admin/owner). Export reports.
Query: format=pdf|csv, programId, status, from, to
🔒 Protected. Personal dashboard metrics for the authenticated user.
Response 200
{
"success": true,
"data": {
"totalSubmissions": 42,
"acceptedReports": 18,
"totalPoints": 1250,
"rank": 7,
"level": "TRACKER",
"recentNotifications": [ {...} ]
}
}🔒 Protected (ADMIN only). Platform-wide analytics.
Response 200
{
"success": true,
"data": {
"totalReports": 430,
"acceptedReports": 190,
"avgTriageHours": 28.4,
"severityDistribution": {
"CRITICAL": 12, "HIGH": 55, "MEDIUM": 98, "LOW": 140, "INFO": 125
},
"topHunters": [ { "fullName": "Alice Chen", "totalPoints": 4800 } ]
}
}Public — no auth required. Verify a certificate's authenticity.
Response 200 (valid certificate)
{
"success": true,
"data": {
"valid": true,
"certificate": {
"verificationId": "550e8400-e29b-41d4-a716-446655440000",
"title": "XSS in PayPal Security #1",
"programName": "PayPal Security",
"severity": "CRITICAL",
"issuedAt": "2026-05-01T00:00:00.000Z",
"snapshots": {
"hunterName": "Alice Chen",
"reportTitle": "XSS in PayPal Security #1",
"severity": "CRITICAL",
"points": 500
},
"pdfUrl": "https://res.cloudinary.com/..."
}
}
}Response 404 (invalid / not found)
{ "success": false, "message": "Certificate not found or has been revoked" }🔒 Protected (certificate owner). Generate or retrieve the PDF certificate.
🔒 Protected (ADMIN). Full paginated report list across all programs.
🔒 Protected (ADMIN). Ban/unban a user account.
🔒 Protected (ADMIN). Approve a pending program.
BugArena uses Socket.io for real-time updates. Authenticate the WebSocket handshake by passing the access token in auth.token:
const socket = io('http://localhost:5000', {
auth: { token: 'eyJ...' }
})| Event | Payload | Description |
|---|---|---|
points_awarded |
{ points, totalPoints, level } |
Hunter receives points for an accepted report |
level_up |
{ oldLevel, newLevel } |
Hunter advances to a new level |
badge_earned |
{ name, description } |
New badge awarded |
report_status_changed |
{ reportId, newStatus } |
Report status updated |
leaderboard_update |
{ topHunters: [...] } |
Periodic leaderboard broadcast |
notification |
{ type, message } |
Generic notification push |
| HTTP | Code | Meaning |
|---|---|---|
400 |
VALIDATION_ERROR |
Request body failed validation |
400 |
SELF_FOLLOW |
Cannot follow yourself |
401 |
NO_TOKEN |
Authorization header / cookie missing |
401 |
TOKEN_EXPIRED |
Access token has expired — refresh it |
401 |
INVALID_TOKEN |
JWT signature invalid |
403 |
ACCOUNT_BANNED |
Account suspended by admin |
403 |
TOTP_REQUIRED |
2FA code required to complete login |
403 |
FORBIDDEN |
Insufficient role for this action |
404 |
USER_NOT_FOUND |
User does not exist |
404 |
REPORT_NOT_FOUND |
Report does not exist |
404 |
PROGRAM_NOT_FOUND |
Program does not exist |
409 |
DUPLICATE_EMAIL |
Email already registered |
422 |
INVALID_STATE_TRANSITION |
Illegal report status change |
423 |
ACCOUNT_LOCKED |
Too many failed logins — try again later |
500 |
INTERNAL_ERROR |
Unexpected server error |
Rate Limits: Login and register endpoints are limited to 20 requests / 15 minutes per IP.
Webhook retries: Outbound Slack/Discord webhooks retry up to 3 times with exponential back-off on failure.