prop/docs/api
Graham McIntire bd731685de
feat(api): ingest endpoint for propmonitor beacon measurements
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.
2026-05-13 16:07:04 -05:00
..
openapi.yaml feat(api): ingest endpoint for propmonitor beacon measurements 2026-05-13 16:07:04 -05:00
README.md docs(api): point users at /users/settings for token creation 2026-05-09 10:15:23 -05:00

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/v2 rather 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 at POST /api/v1/auth/tokens. Either way, your password is never sent to API clients.
  • Format: application/json for 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-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.

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/v1 is 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.