POST /api/v1/beacon-monitor/measurements accepts one measurement per integration window from a propmonitor client, authenticated by the BeaconMonitor token. Records noise floor, signal peak/avg dBFS, SNR, signal-active-fraction, gain, and frequency for later correlation against weather and propagation scores. Beacon UUID must resolve to an approved + on-the-air beacon (404 otherwise — the client drops 404s without retry). Retries with the same (monitor, beacon, measured_at) are idempotent: a unique index makes the second insert a 409. Every accepted upload stamps the monitor's last_seen_at — measurements *are* the heartbeat. MonitorAuth is a separate plug from the user API-token Auth plug; monitors are not users. The rate limiter buckets each monitor by id so retry storms after a 5xx don't burn the shared anon-IP bucket. |
||
|---|---|---|
| .. | ||
| openapi.yaml | ||
| README.md | ||
Microwaveprop REST API
The Microwaveprop public REST API exposes the read-and-write surface a regular user of the website has access to: contact (QSO) submission, beacon submission, beacon-monitor management, propagation queries, and profile management. Admin-only operations (user management, beacon approval, contact moderation) are deliberately excluded from this API.
- Base URL:
https://prop.w5isp.com/api/v1 - Versioning: path-based (
/api/v1). Breaking changes will ship a/api/v2rather than mutate/api/v1. - Auth: opaque bearer tokens (
Authorization: Bearer mwp_...). The easiest way to mint a token is from your account settings page — log in, scroll to API tokens, name it, and copy the value (shown once). You can also mint one programmatically atPOST /api/v1/auth/tokens. Either way, your password is never sent to API clients. - Format:
application/jsonfor requests and successful responses; errors use RFC 9457 problem+json. - OpenAPI 3.1 spec:
openapi.yaml.
Quickstart
# 1. Mint a long-lived bearer token (one-time, requires your password).
curl -sS -X POST https://prop.w5isp.com/api/v1/auth/tokens \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"...","name":"laptop"}'
# => {"data":{...},"token":"mwp_..."} <-- copy the token; it's shown once
# 2. Use the token for everything else.
TOKEN=mwp_...
curl -sS -H "Authorization: Bearer $TOKEN" https://prop.w5isp.com/api/v1/me
Authentication
| Endpoint | Auth | Notes |
|---|---|---|
POST /auth/tokens |
password | The only endpoint that accepts a password. |
GET /me, PATCH /me |
bearer | The user behind the bearer token. |
POST /contacts |
bearer | Regular user QSO submission. |
GET /contacts |
optional | Public; bearer reveals viewer-private rows. |
GET /beacons |
none | Approved beacons only. |
POST /beacons |
bearer | New beacons start unapproved. |
GET /scores |
none | Public read of propagation scores. |
GET /profiles/:call |
none | Public per-callsign profile. |
Token format
Tokens are 32 random bytes URL-base64-encoded, prefixed with mwp_. The
prefix lets static-analysis tools (gitleaks, truffleHog, etc.) match
leaked tokens. Only the SHA-256 of the token is stored server-side.
Token lifecycle
- Tokens may carry an optional
expires_at(ISO 8601 UTC). Without one they live until revoked. GET /me/api-tokenslists every non-revoked token belonging to the authenticated user (without plaintext).DELETE /me/api-tokens/:idrevokes a token (soft delete — the row remains for audit).- Revoking the token used by the current request immediately invalidates every subsequent request from that token.
Errors
All error responses follow RFC 9457:
{
"type": "about:blank",
"title": "validation_failed",
"status": 422,
"detail": "One or more fields are invalid.",
"errors": {
"callsign": ["must be 3-10 letters and digits"]
}
}
| Status | When |
|---|---|
| 400 | Missing/malformed query or body parameter. |
| 401 | No / invalid / revoked / expired bearer token. |
| 403 | Authenticated, but the action is forbidden for the caller. |
| 404 | Resource not found, or hidden by privacy (private contact, etc.). |
| 409 | Duplicate — an equivalent resource already exists. |
| 422 | Validation failed; errors carries per-field messages. |
| 429 | Rate limit exceeded; check RateLimit-* headers + Retry-After. |
| 5xx | Bug. Please open an issue. |
Rate limiting
Each response carries the RFC 9651
RateLimit-* headers:
| Header | Meaning |
|---|---|
RateLimit-Limit |
Requests permitted in the current window. |
RateLimit-Remaining |
Requests remaining in the current window. |
RateLimit-Reset |
Seconds until the window resets. |
Retry-After |
Sent on 429s; retry no sooner than this many secs. |
Defaults:
| Caller | Limit |
|---|---|
| Anonymous (per IP) | 60 req / minute |
| Authenticated (per token) | 600 req / minute |
POST /auth/tokens (per IP) |
30 req / minute |
Endpoint reference
POST /auth/tokens — issue an API token
Prefer the account settings page for interactive use; this endpoint is here for scripts and CLI tools that need to mint a token without leaving the terminal.
Request:
{
"email": "you@example.com",
"password": "your password",
"name": "iPad",
"expires_at": "2027-01-01T00:00:00Z"
}
expires_at is optional; omit it for a token that lives until revoked.
Response (201):
{
"data": {
"id": "01HX...",
"name": "iPad",
"inserted_at": "2026-05-09T12:34:00Z",
"last_used_at": null,
"expires_at": "2027-01-01T00:00:00Z",
"revoked_at": null
},
"token": "mwp_AbCdEf..."
}
GET /me — current user
Returns the authenticated user's profile (callsign, name, email, home QTH, is_admin flag).
PATCH /me — update home QTH
Accepts any subset of home_grid (Maidenhead), home_lat, home_lon,
home_elevation_m. The grid is auto-derived from lat/lon and vice
versa.
GET /me/contacts
Every QSO submitted under the authenticated user's account, newest first.
GET /me/beacons
Every beacon (approved or pending) submitted by the user.
GET /me/api-tokens
List the user's non-revoked API tokens.
DELETE /me/api-tokens/:id
Revoke a token. Returns the updated record (with revoked_at set).
GET /me/beacon-monitors, POST /me/beacon-monitors, DELETE /me/beacon-monitors/:id
CRUD for the user's distributed beacon monitor stations. Each monitor
has a token field — the credential the monitor program uses to
identify itself when reporting.
GET /contacts
Paginated public list of QSOs.
| Query | Default | Notes |
|---|---|---|
page |
1 |
1-based. |
per_page |
50 |
Capped at 200. |
search |
— | One or two callsigns; matches station1 / station2. |
When called with a bearer token, the user's own private contacts are included in addition to the public set.
GET /contacts/:id
A single QSO. Private QSOs return 404 to non-owners.
POST /contacts
Submit a new QSO. Required fields:
{
"station1": "W5XD",
"station2": "K5XD",
"qso_timestamp": "2026-05-08T12:34:00Z",
"band": "10000",
"grid1": "EM12",
"grid2": "EM13",
"mode": "CW"
}
Optional: user_declared_prop_mode, height1_ft, height2_ft,
private, notes. The QSO is automatically attributed to the
authenticated user; their email is recorded as submitter_email.
A duplicate (same stations + same hour + same band) returns 409 Conflict
with the existing record in the existing field.
GET /beacons, GET /beacons/:id, POST /beacons
Approved beacons listing, single-beacon read, and unauthenticated submit
(the new beacon starts in approved=false state until an admin approves
it via the website).
GET /scores/bands
Lists every band the propagation engine scores for, with their humidity- effect classification.
GET /scores
Returns the propagation score + factor breakdown at a grid point.
| Query | Notes |
|---|---|
band |
MHz integer. Required. |
lat, lon |
Decimal degrees. Required. |
valid_time |
ISO 8601 UTC. Optional; defaults to latest hour. |
GET /forecast
The 18-hour score timeline at a grid point. Same band + lat + lon
parameters; returns an array of {valid_time, score} tuples.
GET /profiles/:callsign
Public per-user profile (callsign + name + home QTH) plus all public contacts involving the callsign and all approved beacons submitted by the user. Email is not exposed.
Conventions
- All timestamps are ISO 8601 UTC (
...Z). - All identifiers are UUIDv7 (binary_id) strings.
- Bands are integer MHz strings (
"10000","24000"). - Latitude / longitude are decimal degrees.
- Maidenhead grid squares are 4- or 6-character (
EM12,EM12kx).
Stability promise
- Adding new fields to existing responses is non-breaking.
- Removing or renaming fields will only happen in a new
/api/vN. - Adding new endpoints to
/api/v1is non-breaking. - Tightening validation may produce new 422 responses; these are not considered breaking either, but will be called out in the changelog.
See also
openapi.yaml— the machine-readable spec./.well-known/api-catalog— RFC 9727 service descriptor (public)./algo— the scoring algorithm in detail.