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.
797 lines
23 KiB
YAML
797 lines
23 KiB
YAML
openapi: 3.1.0
|
|
info:
|
|
title: Microwaveprop Public API
|
|
version: "1.0.0"
|
|
summary: REST API for QSO submission, beacon management, and propagation queries.
|
|
description: |
|
|
Public REST API for the NTMS microwave propagation prediction service.
|
|
Implements every action a regular signed-in user can take on the
|
|
website (submit contacts and beacons, manage beacon monitors,
|
|
manage their own API tokens, query propagation scores), plus public
|
|
read-only endpoints for contacts, beacons, profiles, and scores.
|
|
|
|
Admin-only operations (user management, beacon approval, contact
|
|
moderation) are intentionally **not** exposed here.
|
|
|
|
See `docs/api/README.md` for prose, examples, and the stability
|
|
promise.
|
|
contact:
|
|
name: NTMS
|
|
url: https://w5hn.org/
|
|
|
|
servers:
|
|
- url: https://prop.w5isp.com/api/v1
|
|
description: Production
|
|
- url: http://localhost:4000/api/v1
|
|
description: Local development
|
|
|
|
security:
|
|
- bearerAuth: []
|
|
|
|
tags:
|
|
- name: auth
|
|
- name: me
|
|
- name: contacts
|
|
- name: beacons
|
|
- name: monitors
|
|
- name: scores
|
|
- name: profiles
|
|
|
|
paths:
|
|
/auth/tokens:
|
|
post:
|
|
tags: [auth]
|
|
summary: Issue a long-lived bearer token
|
|
security: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/AuthRequest" }
|
|
responses:
|
|
"201":
|
|
description: Token created
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/AuthResponse" }
|
|
"400": { $ref: "#/components/responses/BadRequest" }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"422": { $ref: "#/components/responses/ValidationFailed" }
|
|
"429": { $ref: "#/components/responses/RateLimited" }
|
|
|
|
/me:
|
|
get:
|
|
tags: [me]
|
|
summary: Current user profile
|
|
responses:
|
|
"200":
|
|
description: Profile
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/MeResponse" }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
patch:
|
|
tags: [me]
|
|
summary: Update home QTH
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/HomeQthRequest" }
|
|
responses:
|
|
"200":
|
|
description: Updated profile
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/MeResponse" }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"422": { $ref: "#/components/responses/ValidationFailed" }
|
|
|
|
/me/contacts:
|
|
get:
|
|
tags: [me]
|
|
summary: My QSOs
|
|
responses:
|
|
"200":
|
|
description: List
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/ContactList" }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
|
|
/me/beacons:
|
|
get:
|
|
tags: [me]
|
|
summary: My beacons
|
|
responses:
|
|
"200":
|
|
description: List
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/BeaconList" }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
|
|
/me/api-tokens:
|
|
get:
|
|
tags: [me]
|
|
summary: List my API tokens
|
|
responses:
|
|
"200":
|
|
description: List
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/TokenList" }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
|
|
/me/api-tokens/{id}:
|
|
delete:
|
|
tags: [me]
|
|
summary: Revoke an API token
|
|
parameters:
|
|
- in: path
|
|
name: id
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
responses:
|
|
"200":
|
|
description: Revoked record
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/TokenResponse" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
|
|
/me/beacon-monitors:
|
|
get:
|
|
tags: [monitors]
|
|
summary: List my beacon monitors
|
|
responses:
|
|
"200":
|
|
description: List
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/BeaconMonitorList" }
|
|
post:
|
|
tags: [monitors]
|
|
summary: Create a beacon monitor
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/BeaconMonitorCreate" }
|
|
responses:
|
|
"201":
|
|
description: Created
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/BeaconMonitorResponse" }
|
|
"422": { $ref: "#/components/responses/ValidationFailed" }
|
|
|
|
/me/beacon-monitors/{id}:
|
|
delete:
|
|
tags: [monitors]
|
|
summary: Delete a beacon monitor
|
|
parameters:
|
|
- in: path
|
|
name: id
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
responses:
|
|
"204":
|
|
description: No content
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
|
|
/beacon-monitor/measurements:
|
|
post:
|
|
tags: [monitors]
|
|
summary: Upload a beacon-signal-level measurement
|
|
description: |
|
|
Ingest endpoint for the `propmonitor` client. The Bearer token here
|
|
is the `BeaconMonitor` token (issued once by
|
|
`POST /me/beacon-monitors`), not a user API token. One POST per
|
|
integration window per monitor; payloads are never batched.
|
|
|
|
`signal_peak_dbfs`, `signal_avg_dbfs`, and the `snr_*` fields are
|
|
computed only over FFT frames whose in-band power exceeded
|
|
`noise_floor + 3 dB`. `signal_active_fraction` reports the duty
|
|
cycle of those frames over the window.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/BeaconMeasurementCreate" }
|
|
responses:
|
|
"204":
|
|
description: Accepted and recorded. The monitor's `last_seen_at` is updated.
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"404":
|
|
description: The supplied `beacon_id` is unknown, unapproved, or off-the-air.
|
|
"409":
|
|
description: A measurement for this monitor + beacon + measured_at already exists.
|
|
"422": { $ref: "#/components/responses/ValidationFailed" }
|
|
"429": { $ref: "#/components/responses/RateLimited" }
|
|
|
|
/contacts:
|
|
get:
|
|
tags: [contacts]
|
|
summary: List public QSOs
|
|
security: []
|
|
parameters:
|
|
- in: query
|
|
name: page
|
|
schema: { type: integer, minimum: 1, default: 1 }
|
|
- in: query
|
|
name: per_page
|
|
schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
|
|
- in: query
|
|
name: search
|
|
schema: { type: string }
|
|
responses:
|
|
"200":
|
|
description: Paginated list
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/PaginatedContactList" }
|
|
post:
|
|
tags: [contacts]
|
|
summary: Submit a QSO
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/ContactCreate" }
|
|
responses:
|
|
"201":
|
|
description: Created
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/ContactResponse" }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"409": { $ref: "#/components/responses/Conflict" }
|
|
"422": { $ref: "#/components/responses/ValidationFailed" }
|
|
|
|
/contacts/{id}:
|
|
get:
|
|
tags: [contacts]
|
|
summary: Show a single QSO
|
|
security: []
|
|
parameters:
|
|
- in: path
|
|
name: id
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
responses:
|
|
"200":
|
|
description: QSO
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/ContactResponse" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
|
|
/beacons:
|
|
get:
|
|
tags: [beacons]
|
|
summary: List approved beacons
|
|
security: []
|
|
responses:
|
|
"200":
|
|
description: List
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/BeaconList" }
|
|
post:
|
|
tags: [beacons]
|
|
summary: Submit a beacon
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/BeaconCreate" }
|
|
responses:
|
|
"201":
|
|
description: Created (pending approval)
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/BeaconResponse" }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"422": { $ref: "#/components/responses/ValidationFailed" }
|
|
|
|
/beacons/{id}:
|
|
get:
|
|
tags: [beacons]
|
|
summary: Show a single beacon
|
|
security: []
|
|
parameters:
|
|
- in: path
|
|
name: id
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
responses:
|
|
"200":
|
|
description: Beacon
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/BeaconResponse" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
|
|
/profiles/{callsign}:
|
|
get:
|
|
tags: [profiles]
|
|
summary: Public per-callsign profile
|
|
security: []
|
|
parameters:
|
|
- in: path
|
|
name: callsign
|
|
required: true
|
|
schema: { type: string }
|
|
responses:
|
|
"200":
|
|
description: Profile + contacts + approved beacons
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/ProfileResponse" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
|
|
/scores/bands:
|
|
get:
|
|
tags: [scores]
|
|
summary: Bands with propagation scores
|
|
security: []
|
|
responses:
|
|
"200":
|
|
description: List of bands
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
mhz: { type: integer }
|
|
label: { type: string }
|
|
humidity_effect:
|
|
type: string
|
|
enum: [beneficial, harmful, neutral]
|
|
|
|
/scores:
|
|
get:
|
|
tags: [scores]
|
|
summary: Score + factor breakdown at a grid point
|
|
security: []
|
|
parameters:
|
|
- in: query
|
|
name: band
|
|
required: true
|
|
schema: { type: integer }
|
|
- in: query
|
|
name: lat
|
|
required: true
|
|
schema: { type: number, format: double }
|
|
- in: query
|
|
name: lon
|
|
required: true
|
|
schema: { type: number, format: double }
|
|
- in: query
|
|
name: valid_time
|
|
schema: { type: string, format: date-time }
|
|
responses:
|
|
"200":
|
|
description: Score
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/ScoreResponse" }
|
|
"400": { $ref: "#/components/responses/BadRequest" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
|
|
/forecast:
|
|
get:
|
|
tags: [scores]
|
|
summary: 18-hour score timeline at a grid point
|
|
security: []
|
|
parameters:
|
|
- in: query
|
|
name: band
|
|
required: true
|
|
schema: { type: integer }
|
|
- in: query
|
|
name: lat
|
|
required: true
|
|
schema: { type: number, format: double }
|
|
- in: query
|
|
name: lon
|
|
required: true
|
|
schema: { type: number, format: double }
|
|
responses:
|
|
"200":
|
|
description: Timeline
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
valid_time: { type: string, format: date-time }
|
|
score: { type: integer }
|
|
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: opaque
|
|
|
|
responses:
|
|
BadRequest:
|
|
description: Bad request
|
|
content:
|
|
application/problem+json:
|
|
schema: { $ref: "#/components/schemas/Problem" }
|
|
Unauthorized:
|
|
description: Authentication required
|
|
content:
|
|
application/problem+json:
|
|
schema: { $ref: "#/components/schemas/Problem" }
|
|
NotFound:
|
|
description: Resource not found
|
|
content:
|
|
application/problem+json:
|
|
schema: { $ref: "#/components/schemas/Problem" }
|
|
Conflict:
|
|
description: Duplicate
|
|
content:
|
|
application/problem+json:
|
|
schema: { $ref: "#/components/schemas/Problem" }
|
|
ValidationFailed:
|
|
description: Validation failed
|
|
content:
|
|
application/problem+json:
|
|
schema: { $ref: "#/components/schemas/Problem" }
|
|
RateLimited:
|
|
description: Rate limit exceeded
|
|
headers:
|
|
RateLimit-Limit: { schema: { type: integer } }
|
|
RateLimit-Remaining: { schema: { type: integer } }
|
|
RateLimit-Reset: { schema: { type: integer } }
|
|
Retry-After: { schema: { type: integer } }
|
|
content:
|
|
application/problem+json:
|
|
schema: { $ref: "#/components/schemas/Problem" }
|
|
|
|
schemas:
|
|
Problem:
|
|
type: object
|
|
properties:
|
|
type: { type: string, default: "about:blank" }
|
|
title: { type: string }
|
|
status: { type: integer }
|
|
detail: { type: string }
|
|
errors:
|
|
type: object
|
|
additionalProperties:
|
|
type: array
|
|
items: { type: string }
|
|
|
|
AuthRequest:
|
|
type: object
|
|
required: [email, password, name]
|
|
properties:
|
|
email: { type: string, format: email }
|
|
password: { type: string }
|
|
name: { type: string, maxLength: 100 }
|
|
expires_at: { type: string, format: date-time, nullable: true }
|
|
|
|
AuthResponse:
|
|
type: object
|
|
properties:
|
|
token: { type: string }
|
|
data: { $ref: "#/components/schemas/Token" }
|
|
|
|
Token:
|
|
type: object
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
name: { type: string }
|
|
inserted_at: { type: string, format: date-time }
|
|
last_used_at: { type: string, format: date-time, nullable: true }
|
|
expires_at: { type: string, format: date-time, nullable: true }
|
|
revoked_at: { type: string, format: date-time, nullable: true }
|
|
|
|
TokenList:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/Token" }
|
|
|
|
TokenResponse:
|
|
type: object
|
|
properties:
|
|
data: { $ref: "#/components/schemas/Token" }
|
|
|
|
Me:
|
|
type: object
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
callsign: { type: string }
|
|
name: { type: string }
|
|
email: { type: string, format: email }
|
|
is_admin: { type: boolean }
|
|
confirmed_at: { type: string, format: date-time, nullable: true }
|
|
home_grid: { type: string, nullable: true }
|
|
home_lat: { type: number, format: double, nullable: true }
|
|
home_lon: { type: number, format: double, nullable: true }
|
|
home_elevation_m: { type: integer, nullable: true }
|
|
|
|
MeResponse:
|
|
type: object
|
|
properties:
|
|
data: { $ref: "#/components/schemas/Me" }
|
|
|
|
HomeQthRequest:
|
|
type: object
|
|
properties:
|
|
home_grid: { type: string }
|
|
home_lat: { type: number, format: double }
|
|
home_lon: { type: number, format: double }
|
|
home_elevation_m: { type: integer }
|
|
|
|
Contact:
|
|
type: object
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
station1: { type: string }
|
|
station2: { type: string }
|
|
qso_timestamp: { type: string, format: date-time }
|
|
grid1: { type: string }
|
|
grid2: { type: string }
|
|
pos1:
|
|
type: object
|
|
properties:
|
|
lat: { type: number }
|
|
lon: { type: number }
|
|
pos2:
|
|
type: object
|
|
properties:
|
|
lat: { type: number }
|
|
lon: { type: number }
|
|
mode: { type: string }
|
|
band_mhz: { type: string }
|
|
distance_km: { type: string, nullable: true }
|
|
private: { type: boolean }
|
|
user_declared_prop_mode: { type: string, nullable: true }
|
|
propagation_mechanism: { type: string, nullable: true }
|
|
propagation_mechanism_confidence:
|
|
type: string
|
|
nullable: true
|
|
enum: [high, medium, low]
|
|
notes: { type: string, nullable: true }
|
|
"mine?": { type: boolean }
|
|
|
|
ContactCreate:
|
|
type: object
|
|
required: [station1, station2, qso_timestamp, band, grid1, grid2]
|
|
properties:
|
|
station1: { type: string }
|
|
station2: { type: string }
|
|
qso_timestamp: { type: string, format: date-time }
|
|
band: { type: string }
|
|
grid1: { type: string }
|
|
grid2: { type: string }
|
|
mode: { type: string, enum: [CW, SSB, FM, FT8, FT4, Q65] }
|
|
user_declared_prop_mode: { type: string }
|
|
height1_ft: { type: integer }
|
|
height2_ft: { type: integer }
|
|
private: { type: boolean }
|
|
notes: { type: string, maxLength: 2000 }
|
|
|
|
ContactResponse:
|
|
type: object
|
|
properties:
|
|
data: { $ref: "#/components/schemas/Contact" }
|
|
|
|
ContactList:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/Contact" }
|
|
|
|
PaginatedContactList:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/Contact" }
|
|
meta:
|
|
type: object
|
|
properties:
|
|
page: { type: integer }
|
|
per_page: { type: integer }
|
|
total_entries: { type: integer }
|
|
total_pages: { type: integer }
|
|
|
|
Beacon:
|
|
type: object
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
callsign: { type: string }
|
|
frequency_mhz: { type: number }
|
|
lat: { type: number }
|
|
lon: { type: number }
|
|
grid: { type: string }
|
|
power_mw: { type: number }
|
|
height_ft: { type: integer }
|
|
on_the_air: { type: boolean }
|
|
approved: { type: boolean }
|
|
keying: { type: string }
|
|
bearing: { type: string }
|
|
beamwidth_deg: { type: number, nullable: true }
|
|
notes: { type: string, nullable: true }
|
|
inserted_at: { type: string, format: date-time }
|
|
|
|
BeaconCreate:
|
|
type: object
|
|
required: [frequency_mhz, callsign, lat, lon, power_mw, height_ft, keying]
|
|
properties:
|
|
frequency_mhz: { type: number }
|
|
callsign: { type: string }
|
|
grid: { type: string }
|
|
lat: { type: number }
|
|
lon: { type: number }
|
|
power_mw: { type: number }
|
|
height_ft: { type: integer }
|
|
on_the_air: { type: boolean }
|
|
keying: { type: string }
|
|
bearing: { type: string }
|
|
beamwidth_deg: { type: number }
|
|
notes: { type: string }
|
|
|
|
BeaconResponse:
|
|
type: object
|
|
properties:
|
|
data: { $ref: "#/components/schemas/Beacon" }
|
|
|
|
BeaconList:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/Beacon" }
|
|
|
|
BeaconMonitor:
|
|
type: object
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
name: { type: string }
|
|
token: { type: string, nullable: true }
|
|
last_seen_at: { type: string, format: date-time, nullable: true }
|
|
inserted_at: { type: string, format: date-time }
|
|
|
|
BeaconMonitorCreate:
|
|
type: object
|
|
required: [name]
|
|
properties:
|
|
name: { type: string, maxLength: 100 }
|
|
|
|
BeaconMonitorResponse:
|
|
type: object
|
|
properties:
|
|
data: { $ref: "#/components/schemas/BeaconMonitor" }
|
|
|
|
BeaconMonitorList:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/BeaconMonitor" }
|
|
|
|
BeaconMeasurementCreate:
|
|
type: object
|
|
required:
|
|
- beacon_id
|
|
- frequency_hz
|
|
- measured_at
|
|
- integration_s
|
|
- passband_hz
|
|
- gain_db
|
|
- noise_floor_dbfs
|
|
- signal_peak_dbfs
|
|
- signal_avg_dbfs
|
|
- snr_peak_db
|
|
- snr_avg_db
|
|
- signal_active_fraction
|
|
- propmonitor_version
|
|
properties:
|
|
beacon_id:
|
|
type: string
|
|
format: uuid
|
|
description: The beacon this measurement is for.
|
|
frequency_hz:
|
|
type: integer
|
|
minimum: 1
|
|
description: SDR tuned center frequency in Hz.
|
|
measured_at:
|
|
type: string
|
|
format: date-time
|
|
description: UTC ISO-8601 timestamp at the start of the integration window.
|
|
integration_s:
|
|
type: integer
|
|
minimum: 5
|
|
maximum: 3600
|
|
description: Integration window length in seconds.
|
|
passband_hz:
|
|
type: number
|
|
exclusiveMinimum: 0
|
|
description: Bandwidth of the in-band power window.
|
|
gain_db:
|
|
type: number
|
|
minimum: -10
|
|
maximum: 80
|
|
description: SDR-reported gain at upload time.
|
|
noise_floor_dbfs:
|
|
type: number
|
|
description: Median out-of-passband bin power, dBFS.
|
|
signal_peak_dbfs:
|
|
type: number
|
|
description: Peak in-passband power across detected-on-air frames, dBFS.
|
|
signal_avg_dbfs:
|
|
type: number
|
|
description: Mean in-passband power across detected-on-air frames, dBFS.
|
|
snr_peak_db:
|
|
type: number
|
|
description: signal_peak_dbfs minus noise_floor_dbfs.
|
|
snr_avg_db:
|
|
type: number
|
|
description: signal_avg_dbfs minus noise_floor_dbfs.
|
|
signal_active_fraction:
|
|
type: number
|
|
minimum: 0
|
|
maximum: 1
|
|
description: Fraction of FFT frames where in-band power exceeded noise_floor + 3 dB.
|
|
propmonitor_version:
|
|
type: string
|
|
maxLength: 32
|
|
description: Build version of the reporting client (diagnostic).
|
|
|
|
ProfileResponse:
|
|
type: object
|
|
properties:
|
|
user:
|
|
type: object
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
callsign: { type: string }
|
|
name: { type: string }
|
|
home_grid: { type: string, nullable: true }
|
|
home_lat: { type: number, nullable: true }
|
|
home_lon: { type: number, nullable: true }
|
|
contacts:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/Contact" }
|
|
beacons:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/Beacon" }
|
|
|
|
ScoreResponse:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
lat: { type: number }
|
|
lon: { type: number }
|
|
score: { type: integer }
|
|
factors:
|
|
type: object
|
|
additionalProperties: true
|
|
profile_source:
|
|
oneOf:
|
|
- { type: string, enum: [exact, unavailable] }
|
|
- { type: array }
|
|
valid_time: { type: string, format: date-time }
|