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.

Base URL
https://prop.w5isp.com/api/v1
Versioning
Path-based (/api/v1). Breaking changes ship as /api/v2.
Auth
Opaque bearer tokens (Authorization: Bearer mwp_…). Mint one from your account settings or programmatically at POST /api/v1/auth/tokens.
Format
application/json requests and responses. Errors use RFC 9457 problem+json .
Spec
openapi.yaml (OpenAPI 3.1)

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 account settings page — the value is shown once. Keep it like a password.

Mint a token POST /auth/tokens
curl -sS -X POST https://prop.w5isp.com/api/v1/auth/tokens \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"...","name":"laptop"}'
# => {"data": {...}, "token": "mwp_..."}

TOKEN=mwp_...
curl -sS -H "Authorization: Bearer $TOKEN" \
  https://prop.w5isp.com/api/v1/me

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-tokens lists every non-revoked token belonging to the authenticated user (without plaintext).
  • DELETE /me/api-tokens/:id revokes 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.
Authorization header
Authorization: Bearer mwp_AbCdEf123…
Token format
# 32 random bytes, URL-base64
mwp_<43 chars>

# Server stores SHA-256(token) only.

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.
422 validation failure
{
  "type": "about:blank",
  "title": "validation_failed",
  "status": 422,
  "detail": "One or more fields are invalid.",
  "errors": {
    "callsign": ["must be 3-10 letters and digits"]
  }
}

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
Response headers
RateLimit-Limit: 600
RateLimit-Remaining: 597
RateLimit-Reset: 42

Endpoint reference

POST /auth/tokens

Issue an API token

Prefer the account settings page for interactive use; this endpoint is here for scripts that need to mint a token without leaving the terminal.

Request body

email string required
Account email address.
password string required
Account password.
name string required
Human-readable label so you can identify the token later.
expires_at datetime
ISO 8601 UTC. Omit for a token that lives until revoked.
Request POST /auth/tokens
curl -X POST https://prop.w5isp.com/api/v1/auth/tokens \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "your password",
    "name": "iPad",
    "expires_at": "2027-01-01T00:00:00Z"
  }'
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

The authenticated user

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.

Request GET /me
curl -H "Authorization: Bearer $TOKEN" \
  https://prop.w5isp.com/api/v1/me
Update home QTH PATCH /me
curl -X PATCH \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"home_grid":"EM12kx"}' \
  https://prop.w5isp.com/api/v1/me
GET /me/contacts

User-scoped collections

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.

List my contacts GET /me/contacts
curl -H "Authorization: Bearer $TOKEN" \
  "https://prop.w5isp.com/api/v1/me/contacts?per_page=20"
GET /me/api-tokens

Manage your API tokens

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.

List tokens GET /me/api-tokens
curl -H "Authorization: Bearer $TOKEN" \
  https://prop.w5isp.com/api/v1/me/api-tokens
Revoke a token DELETE /me/api-tokens/:id
curl -X DELETE \
  -H "Authorization: Bearer $TOKEN" \
  https://prop.w5isp.com/api/v1/me/api-tokens/01HX...
GET /me/beacon-monitors

Beacon monitor stations

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.
Create a monitor POST /me/beacon-monitors
curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"shack-pi","grid":"EM12kx"}' \
  https://prop.w5isp.com/api/v1/me/beacon-monitors
GET /contacts

Public contact (QSO) feed

Paginated list of QSOs.

Query parameters

page integer
1-based. Defaults to 1.
per_page integer
Defaults to 50; capped at 200.
search 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.

Search by callsign GET /contacts
curl "https://prop.w5isp.com/api/v1/contacts?search=W5XD&per_page=25"
POST /contacts

Submit a QSO

Create a new QSO under the authenticated user's account; their email is recorded as submitter_email.

Required fields

station1 string required
Submitter callsign.
station2 string required
Other operator's callsign.
qso_timestamp datetime required
ISO 8601 UTC.
band string required
MHz integer string ("10000", "24000").
grid1 string required
4- or 6-character Maidenhead.
grid2 string required
Other end's grid.
mode string required
Mode (e.g. CW, SSB).

Optional fields

user_declared_prop_mode string
e.g. tropo, aircraft_scatter.
height1_ft number
Antenna height above ground.
height2_ft number
Other end's antenna height.
private boolean
Hide from the public feed.
notes string
Free-form text.

A duplicate (same stations + same hour + same band) returns 409 Conflict with the existing record in the existing field.

Submit a QSO POST /contacts
curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "station1": "W5XD",
    "station2": "K5XD",
    "qso_timestamp": "2026-05-08T12:34:00Z",
    "band": "10000",
    "grid1": "EM12",
    "grid2": "EM13",
    "mode": "CW"
  }' \
  https://prop.w5isp.com/api/v1/contacts
GET /beacons

Beacons

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.

Submit a beacon POST /beacons
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "callsign": "WB5LUA/B",
    "frequency_mhz": 10368.025,
    "grid": "EM13qe",
    "power_w": 5
  }' \
  https://prop.w5isp.com/api/v1/beacons
GET /scores

Propagation score at a grid point

Returns the composite propagation score plus the per-factor breakdown (rain, humidity, refractivity, …) for one grid point at one band.

Query parameters

band integer required
MHz.
lat number required
Decimal degrees.
lon number required
Decimal degrees.
valid_time 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).

Score for Dallas, 10 GHz GET /scores
curl "https://prop.w5isp.com/api/v1/scores?band=10000&lat=32.78&lon=-96.80"
GET /forecast

48-hour score timeline

Returns the score at one grid point across the available forecast horizon (currently 48 hours). Same band + lat + lon parameters as /scores.

18 h timeline GET /forecast
curl "https://prop.w5isp.com/api/v1/forecast?band=10000&lat=32.78&lon=-96.80"
GET /profiles/:callsign

Public per-user profile

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.

Read a profile GET /profiles/W5XD
curl https://prop.w5isp.com/api/v1/profiles/W5XD

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/v1 is non-breaking.
  • Tightening validation may produce new 422s; 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.