A Competition Platform for Amateur Radio Operators
Version 1.9.0 — February 2026
Appendices:
Ham Radio Olympics is a web-based competition platform designed for amateur radio clubs to organize and track radio contests throughout a season. Think of it like the actual Olympics: your club runs an "Olympiad" (a competition season), which contains multiple "Sports" (categories of competition like POTA activations or DX hunting), each with individual "Matches" (specific timed events).
The platform integrates directly with QRZ.com and Logbook of The World (LoTW) to automatically sync your QSO logs, calculate scores, and award medals based on both speed (who made the first qualifying contact) and efficiency (best power-to-distance ratio).
Each sport can enable one or both competition modes:
Sports can award medals to hunters and activators in either combined pools (everyone competes together) or separate pools (hunters compete against hunters, activators against activators).
To participate in Ham Radio Olympics, you need to register with your callsign and connect at least one logging service (QRZ.com or LoTW).
Ham Radio Olympics automatically pulls your QSO data from QRZ.com and/or LoTW. This includes confirmation status, which is required for medal eligibility.
To manually trigger a sync of your QSOs:
/sync)
The system will fetch all QSOs from your connected services and match them against active matches in the current Olympiad.
WARNING: Do NOT use the direct WRL → QRZ integration. There is a bug that strips POTA data (SIG/SIG_INFO fields) during the automatic sync, causing your park hunts and activations to not be credited.
Correct workflow for WRL users:
This manual export/import process preserves all POTA park references (MY_SIG_INFO for activations, SIG_INFO for hunts) and ensures your contacts are properly credited for competitions.
After logging in, your Dashboard (/dashboard) is your home base, displaying:
Your public profile is visible to other competitors at /competitor/YOUR_CALLSIGN (e.g., /competitor/W1ABC).
From the main page, you can browse the current Olympiad's sports and matches:
Each sport defines what type of target counts as a qualifying contact:
| Target Type | Description |
|---|---|
| Continent | Contact must be with a station in a specific continent (e.g., EU, AS, AF) |
| Country | Contact must be with a specific DXCC entity |
| Park | Contact must involve a POTA/WWFF park (as hunter or activator) |
| Call | Contact must be with a specific callsign (e.g., special event station) |
| Grid | Contact must be with a station in a specific Maidenhead grid square |
| Any | Any confirmed contact qualifies (useful for general activity periods) |
Every match has two separate medal events:
The QSO Race rewards the first three competitors to log a confirmed qualifying contact with the match target. Speed matters! The first confirmed QSO wins gold, second wins silver, third wins bronze.
Your QSO must be confirmed (via QRZ or LoTW) to count for medals.
The Cool Factor event rewards efficient operating. Your score is calculated as:
Cool Factor = Distance (km) ÷ TX Power (watts)
For example:
The three highest cool factor scores win medals. Ties are broken by earliest QSO time.
Important: Unlike the QSO Race (where the first three confirmed contacts lock in the medals), Cool Factor is an ongoing competition that runs until the match ends. Medal standings can change throughout the match as new QSOs are logged. Even if someone currently holds a gold medal, you can knock them out of medal contention by logging a QSO with a higher cool factor. Keep trying until the match closes!
Some matches may have a maximum power limit for QRP competitions. Only QSOs at or below the specified power will qualify for that match. Check the match details to see if there's a power restriction.
QRP matches encourage low-power operating and reward efficient stations and skilled operators who can make long-distance contacts with minimal power.
Occasionally, a referee or the system may disqualify one of your QSOs from medal contention. This can happen for reasons such as:
What happens when a QSO is disqualified:
Refuting a disqualification:
If you believe a disqualification was made in error, you can submit a refutation:
All disqualification history, including your refutation and any referee responses, is publicly visible for transparency.
| Medal | Points |
|---|---|
| Gold | 3 points |
| Silver | 2 points |
| Bronze | 1 point |
A competitor can only occupy one spot on any podium. This applies to all medal events: match medals, sport standings, and special awards like the Triathlon Podium.
If you have multiple qualifying results (e.g., multiple QSOs in a Cool Factor competition), only your best result counts toward the podium. The remaining spots go to other competitors with the next-best results.
Example: If your QSOs rank 1st and 3rd in a Cool Factor competition, you receive the Gold medal. The Silver goes to whoever had the 2nd-best QSO, and Bronze goes to whoever had the 4th-best (since your 3rd-place QSO doesn't count—you already have Gold).
Park contacts earn bonus points in addition to any medals:
| Scenario | Bonus Points |
|---|---|
| Park-to-Park (both stations at parks) | +2 points |
| Target is a park OR you're at a park | +1 point |
| No park involvement | +0 points |
For activate mode competitions targeting parks, a valid activation requires 10 or more confirmed QSOs from the same park on the same UTC day. This matches POTA's official activation rules.
Important: Once a day qualifies as a valid activation (10+ QSOs), all QSOs from that day count toward medals—including the first 9. The 10-QSO threshold determines whether the day is valid, not which individual QSOs count.
Example:
This requirement only applies to activate mode. Hunters (work mode) have no minimum QSO requirement.
Best possible score (Park-to-Park with double gold):
Strong performance with park bonus:
Solid showing without park involvement:
Efficiency specialist (low power, long distance):
Speed demon (first to make contact):
The Olympiad administrator can set a minimum QSO threshold for medal eligibility. This prevents casual participants from claiming medals over competitors who are actively engaged in the competition.
Why this matters: Without a qualification threshold, someone could make a single exceptional QRP contact (e.g., coast-to-coast on 100mW) and win the Cool Factor gold medal despite not participating in any other matches. The qualification requirement ensures medals go to competitors who are genuinely competing throughout the Olympiad.
How it works:
Teams add a collaborative dimension to Ham Radio Olympics. Team standings are calculated by summing the points of all team members across all sports. You can only be a member of one team at a time.
Any registered competitor can create a team:
/teams)
When you create a team, you automatically become the team captain with special management privileges.
There are two ways to join a team:
/teams
If the captain has already sent you an invitation, your join request is automatically accepted.
Team captains can invite you directly:
If you've already sent a join request to that team, accepting the invitation is automatic.
As a team captain, you have additional management capabilities:
| Action | Description |
|---|---|
| Invite Members | Send invitations to specific competitors to join your team |
| Approve Requests | Accept or decline join requests from competitors |
| Remove Members | Remove a member from the team |
| Transfer Captaincy | Hand over the captain role to another team member |
| Update Team Info | Edit the team name and description |
| Delete Team | Permanently delete the team |
To manage your team, navigate to your team's profile page at /team/{id}.
To leave your current team:
Your individual scores remain intact, but they will no longer contribute to the team total.
Note: Team captains cannot leave the team. You must either transfer captaincy to another member first, or delete the team entirely.
Ham Radio Olympics automatically tracks world records across all competitions. Records are tracked for achievements like:
View current records at /records.
When a confirmed QSO exists that surpasses the current world record but wasn't made during competition, it appears as an "Honorable Mention" below the records table. A QSO is considered "outside competition" if it was either:
This feature recognizes exceptional QSOs while maintaining the integrity of competition records. Think of it as a humbling reminder: someone out there did better when it didn't count.
Your competitor profile tracks your personal best performances:
These are displayed on your dashboard alongside your medal count and total points, allowing you to measure your improvement over time.
The Medal Standings page (/medals) displays all competitors ranked by their total medal count. The leaderboard shows:
This provides a quick overview of who's leading the competition across all sports.
The Triathlon Podium is a unique recognition exclusive to Ham Radio Olympics that highlights QSOs excelling across all three dimensions of amateur radio competition.
Just as Olympic triathlons test athletes across swimming, cycling, and running, the Ham Radio Triathlon measures excellence across three distinct "events":
A single exceptional QSO that scores well in all three events can earn a spot on the Triathlon Podium.
Each qualifying QSO receives a Triathlon Score (maximum 300 points):
| Component | Calculation | Max Points |
|---|---|---|
| Distance Percentile | Your QSO's rank among all QSOs by distance | 100 |
| Cool Factor Percentile | Your QSO's rank among all QSOs by efficiency | 100 |
| POTA Bonus | P2P (both at parks) = 100, single park = 50 | 100 |
Example: A QSO that ranks in the 90th percentile for distance (90 pts), 85th percentile for Cool Factor (85 pts), and is Park-to-Park (100 pts) would score 275 points.
To qualify for the Triathlon Podium, a QSO must:
The Triathlon Podium recognizes a different kind of excellence than traditional medals:
This means a QSO that might not medal in any individual event could still earn Triathlon recognition by performing well across all three dimensions. It celebrates well-rounded operating rather than specialization in a single metric.
The top 3 Triathlon QSOs are displayed at the top of the Records page (/records) with a detailed breakdown showing how each QSO scored in each dimension.
Access your settings from the dropdown menu in the navigation bar.
Manage your account information:
Your Name
Email Address
Password
QRZ API Key
LoTW Credentials
Customize how information is displayed throughout the site:
Distance Unit
Time Display
If you've added and verified an email address, you can receive notifications for:
You can enable or disable each notification type individually, or turn off all notifications at once.
Use the moon/sun icon in the navigation bar to toggle between light and dark mode. The site also respects your system's color scheme preference.
Referees are competitors with elevated privileges for specific sports. The referee role is granted by an administrator and allows you to manage matches and view detailed competitor information within your assigned sport(s).
Once granted referee status, you can access the referee dashboard at /referee. This provides an overview of your assigned sports and any pending tasks.
As a referee for a sport, you can:
Referees can disqualify individual QSOs that violate competition rules. This is different from disqualifying a competitor entirely—a QSO disqualification only affects that specific contact for that specific sport.
Key points about QSO disqualification:
To disqualify a QSO:
Competitor refutation: After a QSO is disqualified, the competitor can submit a refutation explaining why they believe the QSO should be reinstated. Refutations appear in the disqualification history for review.
Requalifying a QSO: If a disqualification was made in error, or after reviewing a competitor's refutation, referees can requalify the QSO. This restores the QSO to medal contention and triggers another medal recomputation.
Automatic disqualification: The system may automatically disqualify QSOs with data anomalies, such as malformed POTA park references (e.g., "K-11" instead of "K-0011"). Competitors can refute these auto-disqualifications, and referees can requalify them after review.
Administrator access can be granted in two ways:
X-Admin-Key header with requests. This key is configured as an environment variable (ADMIN_KEY) during deployment.
To access the admin dashboard, log in with an admin account and navigate to /admin.
An Olympiad is the top-level container for a competition season. To create one:
/admin
Only one Olympiad can be active at a time.
Sports define categories of competition. Configuration options include:
| Setting | Options / Description |
|---|---|
| Target Type | continent, country, park, call, grid, any |
| Interval | daily, weekly, bi-weekly, monthly, quarterly, annually |
| Work Enabled | true/false — allows hunting (working stations) |
| Activate Enabled | true/false — allows activating (being the target) |
| Separate Pools | true/false — if true, hunters and activators compete separately |
Matches are timed events within a sport. Each match requires:
Optional match settings:
Live Results mode allows you to display provisional standings during a match, including QSOs that haven't yet been confirmed. This is useful during competition day events like field days or special event stations, where participants want real-time feedback on their standings.
Live Results mode is ideal for:
When Live Results is enabled for a match:
To enable Live Results for a match:
The match page will immediately begin showing all QSOs (confirmed and unconfirmed) in standings.
Administrators can:
/admin/competitorsAdministrators have full team management capabilities:
/admin/teamsAdministrators can customize the site appearance:
Ham Radio Olympics can send automatic notifications to a Discord channel when key events occur. This keeps your club informed about competition activity in real-time.
| Event | When It Triggers | Deduplication |
|---|---|---|
| New World Records | Someone sets a new distance or cool factor record | Once per record value |
| Medal Awards | Competitors earn gold, silver, or bronze medals | Once per medal |
| New Signups | A new competitor joins | Once per callsign |
| Match Reminders | 7 days, 1 day, and day-of for upcoming matches | Once per match |
| POTA Activity Summary | Every 30 minutes if parks have active spots | Once per 30 minutes |
Each notification type has a distinct appearance in Discord:
| Event | Title | Example Message | Color |
|---|---|---|---|
| World Record | 🥇 New World Record! 📏 | KJ5IRF set a new distance record! Distance: 15,234 km | Gold |
| Medal (Gold) | 🥇 Gold Medal Awarded! | KJ5IRF earned gold in QSO Race | Gold |
| Medal (Silver) | 🥈 Silver Medal Awarded! | W1AW earned silver in Cool Factor | Silver |
| Medal (Bronze) | 🥉 Bronze Medal Awarded! | N0CALL earned bronze in QSO Race | Bronze |
| New Signup | 👋 New Competitor Joined! | Welcome KJ5IRF to Ham Radio Olympics! | Green |
| Match (Today) | 🚀 Match Starting Today! | DX Challenge targeting EU starts today | Red |
| Match (Tomorrow) | ⏰ Match Starting Tomorrow! | POTA Championship targeting K-0001 starts tomorrow | Orange |
| Match (7 days) | 📅 Match in 7 Days | Grid Chase targeting FN31 starts in 7 days | Blue |
| POTA Activity | 📡 POTA Activity (X spots) | • POTA Championship: 5 active spots | Green |
All Discord notifications are deduplicated to prevent spam:
Step 1: Create a Webhook in Discord
https://your-app.fly.dev/static/icon-512.pngStep 2: Add the Webhook to Ham Radio Olympics
https://discord.com/api/webhooks/Administrators can share files with competitors through the Resources system. This is useful for distributing contest rules, schedules, reference documents, ADIF templates, and other materials.
Supported file types: PDF, images (PNG, JPG, GIF, SVG), text files (TXT, MD, CSV), ADIF files (ADI, ADIF), Office documents (DOC, DOCX, XLS, XLSX), and ZIP archives.
Each file can have one or more access rules. A user can download a file if they match any rule:
| Rule | Who Can Access |
|---|---|
| Public | Anyone, including visitors who aren't logged in |
| All Competitors | Any registered, logged-in competitor |
| Admins | Users with administrator privileges |
| Referees | Users with the referee role |
| By Sport | Competitors who have entered a specific sport |
| Individual | Specific callsigns (comma-separated) |
Rules combine with OR logic. For example, a file with "Referees" and "KJ5IRF" access is visible to all referees AND to KJ5IRF specifically.
The admin resources page shows all uploaded files with their title, filename, size, access summary, and upload date. From here you can download or delete any file.
Competitors can browse available resources at /resources (linked in the main navigation). They will only see files they have access to based on the rules above. Anonymous visitors will only see files marked as Public.
| Variable | Description | Required |
|---|---|---|
ENCRYPTION_KEY |
Secret key for encrypting QRZ API keys at rest | Yes |
ADMIN_KEY |
Secret for administrator authentication | Yes |
DATABASE_PATH |
Path to SQLite database file | No (default: ham_olympics.db) |
PORT |
Server port | No (default: 8000) |
To run Ham Radio Olympics locally for development or testing:
pip install -r requirements.txt
ENCRYPTION_KEY, ADMIN_KEY)
python -m uvicorn main:app --reload
Ham Radio Olympics includes a Dockerfile and fly.toml for easy deployment to Fly.io:
curl -L https://fly.io/install.sh | sh
fly auth login
fly.toml.example to fly.toml and update the app name and volume source to match your setup
fly launch --name your-app-name
fly volumes create your_volume_name --size 1 --region iad
fly secrets set ENCRYPTION_KEY="..." ADMIN_KEY="..."
fly deploy
The repository includes a GitHub Actions workflow that automatically deploys to Fly.io whenever you push to the main branch. This means you can make changes, push, and your site updates automatically — no manual fly deploy needed.
Setup:
fly.toml is committed to your repository with your app name and volume configured (see Section 10.3)
fly tokens create deploy -x 999999h
FLY_API_TOKENmain — the workflow at .github/workflows/deploy.yml will handle the rest
Notes:
https://github.com///actions [skip ci] to your commit messageThe project includes a comprehensive test suite. Run tests with:
pytest tests/ -v
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
Landing page |
| GET | /health |
Health check |
| GET | /signup |
Registration page |
| POST | /signup |
Register new competitor |
| GET | /login |
Login page |
| POST | /login |
Authenticate user |
| GET | /forgot-password |
Password reset request page |
| POST | /forgot-password |
Request password reset email |
| GET | /reset-password/{token} |
Password reset page |
| POST | /reset-password/{token} |
Reset password with token |
| GET | /verify-email/{token} |
Verify email address |
These endpoints require a logged-in user:
| Method | Endpoint | Description |
|---|---|---|
| POST | /logout |
Log out current user |
| GET | /dashboard |
User dashboard |
| GET | /settings |
User settings page |
| POST | /settings/name |
Update display name |
| POST | /settings/email |
Update email address |
| POST | /settings/password |
Change password |
| POST | /settings/qrz-key |
Set QRZ API key |
| DELETE | /settings/qrz-key |
Remove QRZ API key |
| POST | /settings/lotw |
Set LoTW credentials |
| DELETE | /settings/lotw |
Remove LoTW credentials |
| GET | /olympiad |
Current Olympiad details |
| GET | /olympiad/sports |
List Sports |
| GET | /olympiad/sport/{id} |
Sport details |
| GET | /olympiad/sport/{id}/participants |
Sport participants |
| GET | /olympiad/sport/{id}/matches |
List Matches |
| GET | /olympiad/sport/{id}/match/{id} |
Match leaderboard |
| POST | /sport/{id}/enter |
Enter a sport |
| POST | /sport/{id}/leave |
Leave a sport |
| GET | /medals |
Medal standings |
| GET | /records |
World records |
| GET | /competitor/{call} |
Competitor profile |
| GET | /sync |
Sync page |
| POST | /sync |
Trigger QRZ/LoTW sync |
| GET | /export/qsos |
Export QSOs (CSV) |
| GET | /export/medals |
Export medals (CSV) |
| GET | /resources |
Browse available resource files |
| GET | /resources/{id}/download |
Download a resource file |
| GET | /teams |
Team listings |
| GET | /team/{id} |
Team profile |
| POST | /team |
Create team |
| PUT | /team/{id} |
Update team (captain only) |
| DELETE | /team/{id} |
Delete team (captain only) |
| POST | /team/{id}/request |
Request to join team |
| POST | /team/{id}/invite/{call} |
Invite member (captain only) |
| POST | /team/{id}/approve/{call} |
Approve join request (captain only) |
| POST | /team/{id}/decline/{call} |
Decline join request (captain only) |
| POST | /team/{id}/accept |
Accept team invitation |
| POST | /team/{id}/reject |
Reject team invitation |
| POST | /team/{id}/leave |
Leave team |
| POST | /team/{id}/remove/{call} |
Remove member (captain only) |
| POST | /team/{id}/transfer/{call} |
Transfer captain role |
These endpoints require referee role for the associated sport:
| Method | Endpoint | Description |
|---|---|---|
| GET | /referee |
Referee dashboard |
| GET | /admin/sport/{id}/matches |
Manage sport matches |
| GET | /admin/sport/{id}/competitors |
View sport competitors |
| POST | /admin/sport/{id}/competitor/{call}/disqualify |
Disqualify competitor from sport |
| POST | /referee/sport/{id}/qso/{qso_id}/disqualify |
Disqualify a QSO |
| POST | /referee/sport/{id}/qso/{qso_id}/requalify |
Requalify a disqualified QSO |
| GET | /qso/{qso_id}/disqualifications |
View QSO disqualification history (public) |
These endpoints require administrator privileges (admin user login or X-Admin-Key header):
| Method | Endpoint | Description |
|---|---|---|
| GET | /admin |
Admin dashboard |
| GET | /admin/audit-log |
View audit log |
| GET | /admin/backup |
Download database backup |
| POST | /admin/recompute-records |
Recompute all medals and records |
| POST | /admin/olympiad |
Create Olympiad |
| GET | /admin/olympiad/{id} |
Get Olympiad details |
| PUT | /admin/olympiad/{id} |
Update Olympiad |
| DELETE | /admin/olympiad/{id} |
Delete Olympiad |
| POST | /admin/olympiad/{id}/activate |
Set as active Olympiad |
| POST | /admin/olympiad/{id}/deactivate |
Deactivate Olympiad |
| GET | /admin/olympiad/{id}/sports |
List sports in Olympiad |
| POST | /admin/olympiad/{id}/sport |
Create Sport |
| GET | /admin/sport/{id} |
Get Sport details |
| PUT | /admin/sport/{id} |
Update Sport |
| DELETE | /admin/sport/{id} |
Delete Sport |
| POST | /admin/sport/{id}/match |
Create Match |
| GET | /admin/match/{id} |
Get Match details |
| PUT | /admin/match/{id} |
Update Match |
| DELETE | /admin/match/{id} |
Delete Match |
| GET | /admin/competitors |
List competitors |
| GET | /admin/export/competitors |
Export competitors (CSV) |
| GET | /admin/export/standings/{id} |
Export standings (CSV) |
| POST | /admin/competitors/bulk-disable |
Bulk disable competitors |
| POST | /admin/competitors/bulk-enable |
Bulk enable competitors |
| POST | /admin/competitors/bulk-delete |
Bulk delete competitors |
| POST | /admin/competitor/{call}/disable |
Disable competitor |
| POST | /admin/competitor/{call}/enable |
Enable competitor |
| POST | /admin/competitor/{call}/set-admin |
Grant admin role |
| POST | /admin/competitor/{call}/remove-admin |
Revoke admin role |
| POST | /admin/competitor/{call}/set-referee |
Grant referee role |
| POST | /admin/competitor/{call}/remove-referee |
Revoke referee role |
| POST | /admin/competitor/{call}/assign-sport/{id} |
Assign referee to sport |
| DELETE | /admin/competitor/{call}/assign-sport/{id} |
Remove referee from sport |
| POST | /admin/competitor/{call}/disqualify |
Disqualify competitor globally |
| POST | /admin/competitor/{call}/reset-password |
Reset competitor password |
| DELETE | /admin/competitor/{call} |
Delete competitor |
| GET | /admin/teams |
List all teams |
| POST | /admin/team |
Create team |
| DELETE | /admin/team/{id} |
Delete team |
| POST | /admin/team/{id}/add/{call} |
Add member to team |
| POST | /admin/team/{id}/remove/{call} |
Remove member from team |
| POST | /admin/team/{id}/transfer/{call} |
Transfer team captain |
| GET | /admin/resources |
Resource file management |
| POST | /admin/resources/upload |
Upload resource file |
| DELETE | /admin/resources/{id} |
Delete resource file |
| Term | Definition |
|---|---|
| Cool Factor | A scoring metric calculated as distance divided by transmit power, rewarding efficient operating. |
| DXCC | DX Century Club, an ARRL award program. Also refers to the list of recognized "entities" (countries/territories) for award purposes. |
| Grid Square | A Maidenhead Locator System designation for geographic location (e.g., FN31). |
| Live Results | A match setting that displays provisional standings including unconfirmed QSOs for real-time feedback during competitions. |
| LoTW | Logbook of The World, an ARRL-operated QSO confirmation service. |
| Match | A timed competition event within a sport, targeting a specific entity. |
| Olympiad | A competition season containing multiple sports. |
| P2P (Park-to-Park) | A contact where both stations are activating parks, earning bonus points. |
| POTA | Parks on the Air, an amateur radio program encouraging portable operations from parks. |
| QRP | Low-power amateur radio operation, typically 5 watts or less for CW and 10 watts or less for SSB. |
| QRZ | QRZ.com, a callsign lookup database and online logbook service. |
| Resources | Admin-uploaded files (rules, schedules, documents) distributed to competitors with granular access control. |
| QSO | A radio contact between two stations. |
| QSO Race | A medal event rewarding the first three confirmed contacts with a match target. |
| Sport | A category of competition within an Olympiad (e.g., DX Challenge, POTA). |
— End of Document —