REST API · v1
Microwaveprop 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 are deliberately excluded.
Quickstart
Mint a long-lived bearer token once, then use it for every
subsequent call. Tokens are 32 random bytes, URL-base64 encoded
with an mwp_ prefix so leak scanners can spot them.
The interactive way to mint a token is the <.link navigate="/users/settings#api-tokens" class="api-docs-link"> account settings page — the value is shown once. Keep it like a password.
<:code> <.code_block title="Mint a token" tag="POST" label="/auth/tokens" code={@sample_quickstart} /> <.row id="authentication"> <:col>Authentication
Every endpoint that mutates state, or returns the authenticated user's own data, requires a bearer token. Public read endpoints accept an optional bearer that unlocks the caller's private rows.
Endpoint auth matrix
| Endpoint | Auth | Notes |
|---|---|---|
POST /auth/tokens |
password | The only endpoint that accepts a password. |
GET / 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 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
— a structured application/problem+json
body
with a stable title
field and per-field errors
on validation failures.
| Status | When |
|---|---|
| 400 | Missing or malformed query / 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. |
| 409 | Duplicate — an equivalent resource already exists. |
| 422 | Validation failed; errors carries per-field messages. |
| 429 |
Rate limit exceeded; check RateLimit-*
+ Retry-After.
|
| 5xx | Bug. Please open an issue. |
Rate limiting
Every response carries the
RFC 9651
RateLimit-*
headers so clients can self-throttle
before hitting a 429.
| 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 seconds. |
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
<.endpoint id="auth-tokens" method="POST" path="/auth/tokens"> <:summary>Issue an API token <:body>Prefer the <.link navigate="/users/settings#api-tokens" class="api-docs-link"> account settings page for interactive use; this endpoint is here for scripts that need to mint a token without leaving the terminal.
Request body
<.properties> <:property name="email" type="string" required> Account email address. <:property name="password" type="string" required> Account password. <:property name="name" type="string" required> Human-readable label so you can identify the token later. <:property name="expires_at" type="datetime"> ISO 8601 UTC. Omit for a token that lives until revoked. <:code> <.code_block title="Request" tag="POST" label="/auth/tokens" code={@sample_auth_tokens_request} /> <.code_block title="Response · 201" lang="json" code={@sample_auth_tokens_response} /> <.endpoint id="me" method="GET" path="/me"> <:summary>The authenticated user <:body>Returns the user behind the bearer token: callsign, name, email, home QTH, and admin flag.
PATCH /me
accepts any subset of home_grid, home_lat, home_lon, home_elevation_m. Grid is auto-derived from
lat/lon and vice versa.
Two sibling endpoints return everything submitted under the authenticated user's account, newest first:
GET /me/contacts— every QSO submitted by the user.-
GET /me/beacons— every beacon (approved or pending) submitted by the user.
Both honour standard page + per_page pagination.
GET /me/api-tokens
lists every non-revoked token belonging to the authenticated
user. The plaintext value is never returned — only the
metadata (id, name, timestamps).
DELETE /me/api-tokens/:id
revokes a token. The row is soft-deleted (its revoked_at
is set) and any in-flight request using it is invalidated on
the next call.
Distributed beacon monitor stations that report SNR samples
back to Microwaveprop. Each monitor row has a token
field — the credential the monitor program uses to
identify itself when reporting.
Operations
GET /me/beacon-monitors— list.-
POST /me/beacon-monitors— create. Returns the new monitor's plaintext token (shown once). DELETE /me/beacon-monitors/:id— revoke and remove.
Paginated list of QSOs.
Query parameters
<.properties> <:property name="page" type="integer"> 1-based. Defaults to1.
<:property name="per_page" type="integer">
Defaults to 50; capped at 200.
<:property name="search" type="string">
One or two callsigns; matches station1 or station2.
When called with a bearer token, the user's own private contacts are included in addition to the public set.
GET /contacts/:id
returns a single QSO. Private QSOs return 404 to non-owners.
Create a new QSO under the authenticated user's account; their
email is recorded as submitter_email.
Required fields
<.properties> <:property name="station1" type="string" required>Submitter callsign. <:property name="station2" type="string" required> Other operator's callsign. <:property name="qso_timestamp" type="datetime" required>ISO 8601 UTC. <:property name="band" type="string" required> MHz integer string ("10000", "24000").
<:property name="grid1" type="string" required>
4- or 6-character Maidenhead.
<:property name="grid2" type="string" required>Other end's grid.
<:property name="mode" type="string" required>
Mode (e.g. CW, SSB).
Optional fields
<.properties> <:property name="user_declared_prop_mode" type="string"> e.g.tropo, aircraft_scatter.
<:property name="height1_ft" type="number">Antenna height above ground.
<:property name="height2_ft" type="number">Other end's antenna height.
<:property name="private" type="boolean">Hide from the public feed.
<:property name="notes" type="string">Free-form text.
A duplicate (same stations + same hour + same band) returns
409 Conflict
with the existing record in the existing
field.
GET /beacons
lists approved beacons; GET /beacons/:id
reads a
single one.
POST /beacons
is unauthenticated — the new beacon is created in approved=false
state until an admin approves it via the website.
Returns the composite propagation score plus the per-factor breakdown (rain, humidity, refractivity, …) for one grid point at one band.
Query parameters
<.properties> <:property name="band" type="integer" required>MHz. <:property name="lat" type="number" required>Decimal degrees. <:property name="lon" type="number" required>Decimal degrees. <:property name="valid_time" type="datetime"> ISO 8601 UTC. Defaults to the latest scored hour.
GET /scores/bands
lists every band the engine scores for, with each band's
humidity-effect classification (beneficial / harmful).
Returns the score at one grid point across the available
forecast horizon (currently 48 hours). Same band
+ lat
+ lon
parameters as /scores.
Public per-user profile: callsign, name, home QTH, all public contacts involving the callsign, plus all approved beacons submitted by the user. Email is not exposed.
<:code> <.code_block title="Read a profile" tag="GET" label="/profiles/W5XD" code={@sample_profile} />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 grids 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 422s; called out in the changelog.
See also
- <.link href="/docs/api/openapi.yaml" class="api-docs-link"> openapi.yaml — the machine-readable spec.
-
/.well-known/api-catalog— RFC 9727 service descriptor (public). - <.link navigate="/algo" class="api-docs-link">/algo — the scoring algorithm in detail.