{"openapi":"3.2.0","info":{"title":"APRS OTA","version":"1","summary":"See who is on the air, the details of any op, and what an operator has done.","description":"A read-only JSON API over the same records aprsota.org renders. This API can't host an op or complete a QSL, that must be done by sending APRS messages. v1 is additive only. Fields and endpoints may appear, but nothing already here changes shape or disappears.","contact":{"url":"https://aprsota.org/handbook/api"}},"servers":[{"url":"https://aprsota.org"}],"security":[{"bearerAuth":[]}],"paths":{"/api/v1/openapi.json":{"get":{"operationId":"get_openapi","summary":"The OpenAPI document for this API.","description":"Unauthenticated, so a developer can import it before they have a token. It describes all public endpoints.","parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}},"headers":{"Cache-Control":{"description":"private, max-age=3600. Honor it. A phone that reopens or refreshes a panel should not fetch again.","schema":{"type":"string"}},"ETag":{"description":"Send it back as If-None-Match to get a 304 when nothing changed.","schema":{"type":"string"}}}}},"security":[]}},"/api/v1/client":{"get":{"operationId":"get_client","summary":"Does my token work?","description":"Answers with the client's name and token creation date. This is an easy way to validate your authentication token works.","parameters":[{"$ref":"#/components/parameters/OTAUser"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/client"},"example":{"name":"APRS Connect","created_at":"2026-09-18T17:00:00Z"}}}},"401":{"$ref":"#/components/responses/401"},"429":{"$ref":"#/components/responses/429"}}}},"/api/v1/now":{"get":{"operationId":"get_now","summary":"The ops on the air right now.","description":"The API's equivalent of /now. It returns all ops on the air right now. Poll it no more than once a minute, send If-None-Match, and honor the Cache-Control it returns.","parameters":[{"$ref":"#/components/parameters/OTAUser"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/now"},"example":{"generated_at":"2026-09-18T20:15:09Z","on_air":[{"callsign":"K0TFU","station":"K0TFU-6","number":12,"state":"on_air","description":"lunch at the lake","reference":{"kind":"pota","code":"US-0072"},"grid":"DN40bs","position":{"lat":40.054167,"lon":-111.525},"country":{"code":"US","name":"United States of America"},"water":{"kind":"lake","name":"Utah Lake"},"on_air_at":"2026-09-18T19:02:41Z","expires_at":"2026-09-18T20:17:00Z","ended_at":null,"end_reason":null,"signoff":null,"qsos":12,"incomplete":2,"points":11,"url":"https://aprsota.org/K0TFU/ops/12","card_image_url":"https://aprsota.org/K0TFU/ops/12/card-3f9a1c2b7d4e.png"}]}}},"headers":{"Cache-Control":{"description":"private, max-age=30. Honor it. A phone that reopens or refreshes a panel should not fetch again.","schema":{"type":"string"}},"ETag":{"description":"Send it back as If-None-Match to get a 304 when nothing changed.","schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/401"},"429":{"$ref":"#/components/responses/429"}}}},"/api/v1/ops":{"get":{"operationId":"get_ops","summary":"Every op that went on the air, most recent first.","description":"For frequent checking, please use /api/v1/now instead of filtering here for ops on the air. The first page of ?state=ended is the website's \"recently completed\".","parameters":[{"name":"page","in":"query","required":false,"description":"The page, 1-based.","schema":{"type":"integer"},"example":1},{"name":"per_page","in":"query","required":false,"description":"Items per page, at most 100.","schema":{"type":"integer"},"example":20},{"name":"state","in":"query","required":false,"description":"Only ops in this state.","schema":{"type":"string","enum":["on_air","ended"]},"example":"ended"},{"$ref":"#/components/parameters/OTAUser"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/op_page"},"example":{"data":[{"callsign":"K0TFU","station":"K0TFU-6","number":12,"state":"on_air","description":"lunch at the lake","reference":{"kind":"pota","code":"US-0072"},"grid":"DN40bs","position":{"lat":40.054167,"lon":-111.525},"country":{"code":"US","name":"United States of America"},"water":{"kind":"lake","name":"Utah Lake"},"on_air_at":"2026-09-18T19:02:41Z","expires_at":"2026-09-18T20:17:00Z","ended_at":null,"end_reason":null,"signoff":null,"qsos":12,"incomplete":2,"points":11,"url":"https://aprsota.org/K0TFU/ops/12","card_image_url":"https://aprsota.org/K0TFU/ops/12/card-3f9a1c2b7d4e.png"}],"page":{"number":1,"size":20,"total":61,"next_url":"https://aprsota.org/api/v1/ops?page=2"}}}},"headers":{"Cache-Control":{"description":"private, max-age=60. Honor it. A phone that reopens or refreshes a panel should not fetch again.","schema":{"type":"string"}},"ETag":{"description":"Send it back as If-None-Match to get a 304 when nothing changed.","schema":{"type":"string"}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"429":{"$ref":"#/components/responses/429"}}}},"/api/v1/plans":{"get":{"operationId":"get_plans","summary":"Active plans, soonest first.","description":"Plans whose time is still in the future, the same set /plans shows. Canceled plans don't show here, nor do plans that because ops on the air.","parameters":[{"name":"page","in":"query","required":false,"description":"The page, 1-based.","schema":{"type":"integer"},"example":1},{"name":"per_page","in":"query","required":false,"description":"Items per page, at most 100.","schema":{"type":"integer"},"example":20},{"$ref":"#/components/parameters/OTAUser"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/plan_page"},"example":{"data":[{"callsign":"K0TFU","station":"K0TFU-6","starts_at":"2026-09-19T18:00:00Z","description":"POTA US-0072","url":"https://aprsota.org/K0TFU"}],"page":{"number":1,"size":20,"total":61,"next_url":"https://aprsota.org/api/v1/ops?page=2"}}}},"headers":{"Cache-Control":{"description":"private, max-age=60. Honor it. A phone that reopens or refreshes a panel should not fetch again.","schema":{"type":"string"}},"ETag":{"description":"Send it back as If-None-Match to get a 304 when nothing changed.","schema":{"type":"string"}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"429":{"$ref":"#/components/responses/429"}}}},"/api/v1/operators/{callsign}":{"get":{"operationId":"get_operator","summary":"Information about an operator: identity, totals, a live op if there is one, and recent activity.","description":"The totals are computed for all time; the full standings are in a separate scoreboard endpoint.","parameters":[{"name":"callsign","in":"path","required":true,"description":"The operator's base call. An SSID form or mixed case answers 301 to the canonical URL.","schema":{"type":"string"},"example":"K0TFU"},{"$ref":"#/components/parameters/OTAUser"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/operator"},"example":{"callsign":"K0TFU","first_heard_at":"2026-07-22T01:14:09Z","last_heard_at":"2026-09-18T19:17:02Z","totals":{"points":123,"qsos":45,"ops_hosted":12},"on_air":{"callsign":"K0TFU","station":"K0TFU-6","number":12,"state":"on_air","description":"lunch at the lake","reference":{"kind":"pota","code":"US-0072"},"grid":"DN40bs","position":{"lat":40.054167,"lon":-111.525},"country":{"code":"US","name":"United States of America"},"water":{"kind":"lake","name":"Utah Lake"},"on_air_at":"2026-09-18T19:02:41Z","expires_at":"2026-09-18T20:17:00Z","ended_at":null,"end_reason":null,"signoff":null,"qsos":12,"incomplete":2,"points":11,"url":"https://aprsota.org/K0TFU/ops/12","card_image_url":"https://aprsota.org/K0TFU/ops/12/card-3f9a1c2b7d4e.png"},"recent_ops":[{"callsign":"K0TFU","station":"K0TFU-6","number":12,"state":"on_air","description":"lunch at the lake","reference":{"kind":"pota","code":"US-0072"},"grid":"DN40bs","position":{"lat":40.054167,"lon":-111.525},"country":{"code":"US","name":"United States of America"},"water":{"kind":"lake","name":"Utah Lake"},"on_air_at":"2026-09-18T19:02:41Z","expires_at":"2026-09-18T20:17:00Z","ended_at":null,"end_reason":null,"signoff":null,"qsos":12,"incomplete":2,"points":11,"url":"https://aprsota.org/K0TFU/ops/12","card_image_url":"https://aprsota.org/K0TFU/ops/12/card-3f9a1c2b7d4e.png"}],"recent_qsos":[{"host":"K0TFU","host_station":"K0TFU-6","op_number":12,"chaser":"W1XYZ","chaser_station":"W1XYZ-5","number":1,"state":"confirmed","chased_at":"2026-09-18T19:14:19Z","beacon_at":"2026-09-18T19:15:02Z","confirmed_at":"2026-09-18T19:17:02Z","chase_text":"hi from the lake","qsl_text":"73","grid":"CN87xo","position":{"lat":40.054167,"lon":-111.525},"country":{"code":"US","name":"United States of America"},"water":{"kind":"lake","name":"Utah Lake"},"points":3,"stamps":[{"kind":"ptt","title":"PTT","role":"host","points":1,"label":null}],"combos":[],"repeat":false,"url":"https://aprsota.org/K0TFU/ops/12/qsos/W1XYZ","qsl_image_url":"https://aprsota.org/K0TFU/ops/12/qsos/W1XYZ/qsl-9b2e4d1f6a3c.png"}],"url":"https://aprsota.org/K0TFU","image_url":"https://aprsota.org/K0TFU/og-1a2b3c4d5e6f.png"}}},"headers":{"Cache-Control":{"description":"private, max-age=60. Honor it. A phone that reopens or refreshes a panel should not fetch again.","schema":{"type":"string"}},"ETag":{"description":"Send it back as If-None-Match to get a 304 when nothing changed.","schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/401"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"}}}},"/api/v1/operators/{callsign}/scoreboard":{"get":{"operationId":"get_scoreboard","summary":"The operator's standings on every board, for this month, this year, and all time.","description":"The operator's positions on various leaderboards, one row per board the operator holds a value on, with the value in the board's own currency and the rank when on the board. Its own endpoint because its expensive to produce.","parameters":[{"name":"callsign","in":"path","required":true,"description":"The operator's base call. An SSID form or mixed case answers 301 to the canonical URL.","schema":{"type":"string"},"example":"K0TFU"},{"$ref":"#/components/parameters/OTAUser"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/scoreboard"},"example":{"callsign":"K0TFU","windows":[{"kind":"month","from":"2026-09-01","to":"2026-09-30"}],"rows":[{"board":{"slug":"all-star","name":"All-Star","measure":"Points","unit":"points","url":"https://aprsota.org/leaderboards/all-star"},"cells":[{"value":22,"rank":3}]}]}}},"headers":{"Cache-Control":{"description":"private, max-age=60. Honor it. A phone that reopens or refreshes a panel should not fetch again.","schema":{"type":"string"}},"ETag":{"description":"Send it back as If-None-Match to get a 304 when nothing changed.","schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/401"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"}}}},"/api/v1/operators/{callsign}/ops":{"get":{"operationId":"get_operator_ops","summary":"The operator's hosted ops, newest first.","description":"Every op this operator hosted that reached the air, including any op that is on the air right now.","parameters":[{"name":"callsign","in":"path","required":true,"description":"The operator's base call. An SSID form or mixed case answers 301 to the canonical URL.","schema":{"type":"string"},"example":"K0TFU"},{"name":"page","in":"query","required":false,"description":"The page, 1-based.","schema":{"type":"integer"},"example":1},{"name":"per_page","in":"query","required":false,"description":"Items per page, at most 100.","schema":{"type":"integer"},"example":20},{"name":"state","in":"query","required":false,"description":"Only ops in this state.","schema":{"type":"string","enum":["on_air","ended"]},"example":"ended"},{"$ref":"#/components/parameters/OTAUser"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/op_page"},"example":{"data":[{"callsign":"K0TFU","station":"K0TFU-6","number":12,"state":"on_air","description":"lunch at the lake","reference":{"kind":"pota","code":"US-0072"},"grid":"DN40bs","position":{"lat":40.054167,"lon":-111.525},"country":{"code":"US","name":"United States of America"},"water":{"kind":"lake","name":"Utah Lake"},"on_air_at":"2026-09-18T19:02:41Z","expires_at":"2026-09-18T20:17:00Z","ended_at":null,"end_reason":null,"signoff":null,"qsos":12,"incomplete":2,"points":11,"url":"https://aprsota.org/K0TFU/ops/12","card_image_url":"https://aprsota.org/K0TFU/ops/12/card-3f9a1c2b7d4e.png"}],"page":{"number":1,"size":20,"total":61,"next_url":"https://aprsota.org/api/v1/ops?page=2"}}}},"headers":{"Cache-Control":{"description":"private, max-age=60. Honor it. A phone that reopens or refreshes a panel should not fetch again.","schema":{"type":"string"}},"ETag":{"description":"Send it back as If-None-Match to get a 304 when nothing changed.","schema":{"type":"string"}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"}}}},"/api/v1/operators/{callsign}/ops/{number}":{"get":{"operationId":"get_operator_op","summary":"The summary and logbook of a single op.","description":"A chaser must have sent both the message and the position report in order for it to show in the entries, which are in chronological order.","parameters":[{"name":"callsign","in":"path","required":true,"description":"The operator's base call. An SSID form or mixed case answers 301 to the canonical URL.","schema":{"type":"string"},"example":"K0TFU"},{"name":"number","in":"path","required":true,"description":"The op's per-host number.","schema":{"type":"integer"},"example":12},{"$ref":"#/components/parameters/OTAUser"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/op_detail"},"example":{"callsign":"K0TFU","station":"K0TFU-6","number":12,"state":"on_air","description":"lunch at the lake","reference":{"kind":"pota","code":"US-0072"},"grid":"DN40bs","position":{"lat":40.054167,"lon":-111.525},"country":{"code":"US","name":"United States of America"},"water":{"kind":"lake","name":"Utah Lake"},"on_air_at":"2026-09-18T19:02:41Z","expires_at":"2026-09-18T20:17:00Z","ended_at":null,"end_reason":null,"signoff":null,"qsos":12,"incomplete":2,"points":11,"url":"https://aprsota.org/K0TFU/ops/12","card_image_url":"https://aprsota.org/K0TFU/ops/12/card-3f9a1c2b7d4e.png","host":{"callsign":"K0TFU","first_heard_at":"2026-07-22T01:14:09Z","last_heard_at":"2026-09-18T19:17:02Z","url":"https://aprsota.org/K0TFU"},"entries":[{"host":"K0TFU","host_station":"K0TFU-6","op_number":12,"chaser":"W1XYZ","chaser_station":"W1XYZ-5","number":1,"state":"confirmed","chased_at":"2026-09-18T19:14:19Z","beacon_at":"2026-09-18T19:15:02Z","confirmed_at":"2026-09-18T19:17:02Z","chase_text":"hi from the lake","qsl_text":"73","grid":"CN87xo","position":{"lat":40.054167,"lon":-111.525},"country":{"code":"US","name":"United States of America"},"water":{"kind":"lake","name":"Utah Lake"},"points":3,"stamps":[{"kind":"ptt","title":"PTT","role":"host","points":1,"label":null}],"combos":[],"repeat":false,"url":"https://aprsota.org/K0TFU/ops/12/qsos/W1XYZ","qsl_image_url":"https://aprsota.org/K0TFU/ops/12/qsos/W1XYZ/qsl-9b2e4d1f6a3c.png"}],"countries":1}}},"headers":{"Cache-Control":{"description":"private, max-age=60. Honor it. A phone that reopens or refreshes a panel should not fetch again.","schema":{"type":"string"}},"ETag":{"description":"Send it back as If-None-Match to get a 304 when nothing changed.","schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/401"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"}}}},"/api/v1/operators/{callsign}/qsos":{"get":{"operationId":"get_operator_qsos","summary":"The operator's logbook, including QSOs from hosted ops and chased ops, newest first.","description":"Every confirmed QSO the operator was part of, as host or chaser, the same set the logbook page shows.","parameters":[{"name":"callsign","in":"path","required":true,"description":"The operator's base call. An SSID form or mixed case answers 301 to the canonical URL.","schema":{"type":"string"},"example":"K0TFU"},{"name":"page","in":"query","required":false,"description":"The page, 1-based.","schema":{"type":"integer"},"example":1},{"name":"per_page","in":"query","required":false,"description":"Items per page, at most 100.","schema":{"type":"integer"},"example":20},{"name":"role","in":"query","required":false,"description":"Only QSOs where the operator played this role.","schema":{"type":"string","enum":["host","chaser"]},"example":"host"},{"$ref":"#/components/parameters/OTAUser"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/qso_page"},"example":{"data":[{"host":"K0TFU","host_station":"K0TFU-6","op_number":12,"chaser":"W1XYZ","chaser_station":"W1XYZ-5","number":1,"state":"confirmed","chased_at":"2026-09-18T19:14:19Z","beacon_at":"2026-09-18T19:15:02Z","confirmed_at":"2026-09-18T19:17:02Z","chase_text":"hi from the lake","qsl_text":"73","grid":"CN87xo","position":{"lat":40.054167,"lon":-111.525},"country":{"code":"US","name":"United States of America"},"water":{"kind":"lake","name":"Utah Lake"},"points":3,"stamps":[{"kind":"ptt","title":"PTT","role":"host","points":1,"label":null}],"combos":[],"repeat":false,"url":"https://aprsota.org/K0TFU/ops/12/qsos/W1XYZ","qsl_image_url":"https://aprsota.org/K0TFU/ops/12/qsos/W1XYZ/qsl-9b2e4d1f6a3c.png"}],"page":{"number":1,"size":20,"total":61,"next_url":"https://aprsota.org/api/v1/ops?page=2"}}}},"headers":{"Cache-Control":{"description":"private, max-age=60. Honor it. A phone that reopens or refreshes a panel should not fetch again.","schema":{"type":"string"}},"ETag":{"description":"Send it back as If-None-Match to get a 304 when nothing changed.","schema":{"type":"string"}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"}}}},"/api/v1/operators/{callsign}/ops/{number}/qsos/{chaser}":{"get":{"operationId":"get_operator_qso","summary":"The chaser's first confirmed QSO on this op.","description":"The information on the digital QSL card. Confirmed QSOs only.","parameters":[{"name":"callsign","in":"path","required":true,"description":"The operator's base call. An SSID form or mixed case answers 301 to the canonical URL.","schema":{"type":"string"},"example":"K0TFU"},{"name":"number","in":"path","required":true,"description":"The op's per-host number.","schema":{"type":"integer"},"example":12},{"name":"chaser","in":"path","required":true,"description":"The chaser's base call; SSID forms and other cases 301.","schema":{"type":"string"},"example":"W1XYZ"},{"$ref":"#/components/parameters/OTAUser"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/qso"},"example":{"host":"K0TFU","host_station":"K0TFU-6","op_number":12,"chaser":"W1XYZ","chaser_station":"W1XYZ-5","number":1,"state":"confirmed","chased_at":"2026-09-18T19:14:19Z","beacon_at":"2026-09-18T19:15:02Z","confirmed_at":"2026-09-18T19:17:02Z","chase_text":"hi from the lake","qsl_text":"73","grid":"CN87xo","position":{"lat":40.054167,"lon":-111.525},"country":{"code":"US","name":"United States of America"},"water":{"kind":"lake","name":"Utah Lake"},"points":3,"stamps":[{"kind":"ptt","title":"PTT","role":"host","points":1,"label":null}],"combos":[],"repeat":false,"url":"https://aprsota.org/K0TFU/ops/12/qsos/W1XYZ","qsl_image_url":"https://aprsota.org/K0TFU/ops/12/qsos/W1XYZ/qsl-9b2e4d1f6a3c.png"}}},"headers":{"Cache-Control":{"description":"private, max-age=60. Honor it. A phone that reopens or refreshes a panel should not fetch again.","schema":{"type":"string"}},"ETag":{"description":"Send it back as If-None-Match to get a 304 when nothing changed.","schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/401"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"}}}},"/api/v1/operators/{callsign}/ops/{number}/qsos/{chaser}/{contact}":{"get":{"operationId":"get_operator_qso_contact","summary":"One contact by ordinal: a chaser's second or later QSO on this op.","description":"A repeat is still a QSO with its own page and card but it doesn't score any points. An explicit /1 answers 301 to the bare form.","parameters":[{"name":"callsign","in":"path","required":true,"description":"The operator's base call. An SSID form or mixed case answers 301 to the canonical URL.","schema":{"type":"string"},"example":"K0TFU"},{"name":"number","in":"path","required":true,"description":"The op's per-host number.","schema":{"type":"integer"},"example":12},{"name":"chaser","in":"path","required":true,"description":"The chaser's base call.","schema":{"type":"string"},"example":"W1XYZ"},{"name":"contact","in":"path","required":true,"description":"The contact's ordinal on the op, 2 or more.","schema":{"type":"integer"},"example":2},{"$ref":"#/components/parameters/OTAUser"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/qso"},"example":{"host":"K0TFU","host_station":"K0TFU-6","op_number":12,"chaser":"W1XYZ","chaser_station":"W1XYZ-5","number":1,"state":"confirmed","chased_at":"2026-09-18T19:14:19Z","beacon_at":"2026-09-18T19:15:02Z","confirmed_at":"2026-09-18T19:17:02Z","chase_text":"hi from the lake","qsl_text":"73","grid":"CN87xo","position":{"lat":40.054167,"lon":-111.525},"country":{"code":"US","name":"United States of America"},"water":{"kind":"lake","name":"Utah Lake"},"points":3,"stamps":[{"kind":"ptt","title":"PTT","role":"host","points":1,"label":null}],"combos":[],"repeat":false,"url":"https://aprsota.org/K0TFU/ops/12/qsos/W1XYZ","qsl_image_url":"https://aprsota.org/K0TFU/ops/12/qsos/W1XYZ/qsl-9b2e4d1f6a3c.png"}}},"headers":{"Cache-Control":{"description":"private, max-age=60. Honor it. A phone that reopens or refreshes a panel should not fetch again.","schema":{"type":"string"}},"ETag":{"description":"Send it back as If-None-Match to get a 304 when nothing changed.","schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/401"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A per-developer token, minted by hand and presented as `Authorization: Bearer aprsota_...`. Ask for one at the address the handbook gives."}},"parameters":{"OTAUser":{"name":"OTA-User","in":"header","required":false,"description":"An opaque identifier for the app's user, at most 64 characters, for the usage ledger. A random value created at app install. Never a callsign, an email, or anything else that identifies a person. OK to send empty if you don't track or enroll users.","schema":{"type":"string","maxLength":64}}},"schemas":{"position":{"type":"object","description":"A place on the earth, as parsed from an APRS position report.","required":["lat","lon"],"properties":{"lat":{"type":"number","description":"Latitude in signed decimal degrees."},"lon":{"type":"number","description":"Longitude in signed decimal degrees."}}},"country":{"type":"object","description":"The country a position resolved to, if it's is in a country.","required":["code","name"],"properties":{"code":{"type":["string","null"],"description":"ISO 3166-1 alpha-2 code, or null for a feature with a name and no code."},"name":{"type":["string","null"],"description":"The country's English name."}}},"water":{"type":"object","description":"The body of water a position is on, when it is on one.","required":["kind","name"],"properties":{"kind":{"type":"string","description":"The kind of water the position lookup found."},"name":{"type":["string","null"],"description":"The body of water's name, when the map has one."}}},"reference":{"type":"object","description":"POTA park or SOTA summit associated with the position, if it can be determined.","required":["kind","code"],"properties":{"kind":{"type":"string","enum":["pota","sota"],"description":"pota or sota."},"code":{"type":"string","description":"The reference as the host gave it."}}},"stamp":{"type":"object","description":"One stamp minted on a QSO and its points.","required":["kind","title","role","points","label"],"properties":{"kind":{"type":"string","description":"The stamp's catalog kind."},"title":{"type":["string","null"],"description":"The stamp's name, or null for a kind the catalog no longer knows."},"role":{"type":"string","enum":["host","chaser","both"],"description":"Which party earned it."},"points":{"type":"integer","description":"What this stamp scored on this QSO."},"label":{"type":["string","null"],"description":"A fact the stamp carries, such as the body of water; usually null."}}},"combo":{"type":"object","description":"A name for a pattern of stamps on a QSO.","required":["kind","title","role"],"properties":{"kind":{"type":"string","description":"The combo's catalog kind."},"title":{"type":["string","null"],"description":"The combo's name, or null for a kind the catalog no longer knows."},"role":{"type":"string","enum":["host","chaser","both"],"description":"Which party the pattern belongs to."}}},"operator_summary":{"type":"object","description":"An operator's identity.","required":["callsign","first_heard_at","last_heard_at","url"],"properties":{"callsign":{"type":"string","description":"The base callsign, the identity behind every SSID."},"first_heard_at":{"type":["string","null"],"format":"date-time","description":"When OTA first heard any station of this call."},"last_heard_at":{"type":["string","null"],"format":"date-time","description":"When OTA last heard any station of this call."},"url":{"type":"string","format":"uri","description":"The operator page."}}},"totals":{"type":"object","description":"Operator totals.","required":["points","qsos","ops_hosted"],"properties":{"points":{"type":"integer","description":"Points from every confirmed QSO, both roles (the All-Star measure)."},"qsos":{"type":"integer","description":"Confirmed QSOs, both roles (the QSO Machine measure)."},"ops_hosted":{"type":"integer","description":"Ops hosted that reached the air and have ended."}}},"op":{"type":"object","description":"Summary data for an on-air session by a host.","required":["callsign","station","number","state","description","reference","grid","position","country","water","on_air_at","expires_at","ended_at","end_reason","signoff","qsos","incomplete","points","url","card_image_url"],"properties":{"callsign":{"type":"string","description":"The host's base callsign."},"station":{"type":"string","description":"The station running the op, SSID included. Every host command comes from it."},"number":{"type":"integer","description":"The op's per-host number, the {n} in its URL."},"state":{"type":"string","enum":["on_air","ended"],"description":"on_air or ended. An op is not public until its confirming beacon lands."},"description":{"type":["string","null"],"description":"What the host typed after HOST."},"reference":{"oneOf":[{"$ref":"#/components/schemas/reference"},{"type":"null"}],"description":"The POTA or SOTA reference the host named, or null."},"grid":{"type":["string","null"],"description":"The six-character Maidenhead locator, or null when the position is not a place."},"position":{"oneOf":[{"$ref":"#/components/schemas/position"},{"type":"null"}],"description":"Where the confirming beacon placed the host. Established once and never moved."},"country":{"oneOf":[{"$ref":"#/components/schemas/country"},{"type":"null"}],"description":"The country the position resolved to, or null."},"water":{"oneOf":[{"$ref":"#/components/schemas/water"},{"type":"null"}],"description":"The body of water the position is on, or null on dry land."},"on_air_at":{"type":"string","format":"date-time","description":"When the confirming beacon landed."},"expires_at":{"type":["string","null"],"format":"date-time","description":"When the op goes off the air unless extended. Null once ended. Derive minutes left from this."},"ended_at":{"type":["string","null"],"format":"date-time","description":"When the op ended, or null while on the air."},"end_reason":{"type":["string","null"],"enum":["qrt","idle_timeout","hard_cap",null],"description":"Why it ended: qrt (a DONE), idle_timeout, or hard_cap. Null while on the air."},"signoff":{"type":["string","null"],"description":"The host's words on their DONE, or null."},"qsos":{"type":"integer","description":"Confirmed QSOs."},"incomplete":{"type":"integer","description":"Entries with no QSL: chasing while on the air, incomplete once ended."},"points":{"type":"integer","description":"The op's score: the sum of its QSOs' points."},"url":{"type":"string","format":"uri","description":"The op page."},"card_image_url":{"type":["string","null"],"format":"uri","description":"The op's share card, digest included, so the URL changes when the card does."}}},"qso":{"type":"object","description":"One logbook entry. A chase is present whether the QSL was sent or not.","required":["host","host_station","op_number","chaser","chaser_station","number","state","chased_at","beacon_at","confirmed_at","chase_text","qsl_text","grid","position","country","water","points","stamps","combos","repeat","url","qsl_image_url"],"properties":{"host":{"type":"string","description":"The host's base call."},"host_station":{"type":"string","description":"The station that ran the op, SSID included."},"op_number":{"type":"integer","description":"The op's per-host number."},"chaser":{"type":"string","description":"The chaser's base call."},"chaser_station":{"type":"string","description":"The station that chased, SSID included."},"number":{"type":["integer","null"],"description":"The contact's ordinal among this chaser's confirmed contacts on the op, the {k} in its URL. Null until confirmed."},"state":{"type":"string","enum":["relayed","confirmed","unanswered"],"description":"relayed (the host has it), confirmed (the QSL came), or unanswered (the op ended first)."},"chased_at":{"type":"string","format":"date-time","description":"When the chase message arrived. Shown as \"Start\" on the logbook."},"beacon_at":{"type":["string","null"],"format":"date-time","description":"When the chaser's position report was received."},"confirmed_at":{"type":["string","null"],"format":"date-time","description":"When the QSL arrived. Shown on the logbook as \"End\". Null if no QSL sent."},"chase_text":{"type":["string","null"],"description":"What the chaser said after the callsign."},"qsl_text":{"type":["string","null"],"description":"What the host said after QSL."},"grid":{"type":["string","null"],"description":"The chaser's six-character grid, or null when their position is not a place."},"position":{"oneOf":[{"$ref":"#/components/schemas/position"},{"type":"null"}],"description":"Where the chaser's beacon placed them. Established once."},"country":{"oneOf":[{"$ref":"#/components/schemas/country"},{"type":"null"}],"description":"The chaser's country, or null."},"water":{"oneOf":[{"$ref":"#/components/schemas/water"},{"type":"null"}],"description":"The body of water the chaser was on, or null."},"points":{"type":"integer","description":"The QSO's points calculated by the sum of its stamps. 0 on a repeat."},"stamps":{"type":"array","items":{"$ref":"#/components/schemas/stamp"},"description":"The stamps minted on this QSO."},"combos":{"type":"array","items":{"$ref":"#/components/schemas/combo"},"description":"The combos matched on this QSO."},"repeat":{"type":"boolean","description":"A second or later contact by this chaser on this op. Logged but doesn't score points."},"url":{"type":["string","null"],"format":"uri","description":"The URL to the QSO page which shows the digital QSL card. Null until confirmed."},"qsl_image_url":{"type":["string","null"],"format":"uri","description":"The QSL card image. Null until confirmed."}}},"op_detail":{"type":"object","description":"One op including every logbook entry.","required":["callsign","station","number","state","description","reference","grid","position","country","water","on_air_at","expires_at","ended_at","end_reason","signoff","qsos","incomplete","points","url","card_image_url","host","entries","countries"],"properties":{"callsign":{"type":"string","description":"The host's base callsign."},"station":{"type":"string","description":"The station running the op, SSID included. Every host command comes from it."},"number":{"type":"integer","description":"The op's per-host number, the {n} in its URL."},"state":{"type":"string","enum":["on_air","ended"],"description":"on_air or ended. An op is not public until its confirming beacon lands."},"description":{"type":["string","null"],"description":"What the host typed after HOST."},"reference":{"oneOf":[{"$ref":"#/components/schemas/reference"},{"type":"null"}],"description":"The POTA or SOTA reference the host named, or null."},"grid":{"type":["string","null"],"description":"The six-character Maidenhead locator, or null when the position is not a place."},"position":{"oneOf":[{"$ref":"#/components/schemas/position"},{"type":"null"}],"description":"Where the confirming beacon placed the host. Established once and never moved."},"country":{"oneOf":[{"$ref":"#/components/schemas/country"},{"type":"null"}],"description":"The country the position resolved to, or null."},"water":{"oneOf":[{"$ref":"#/components/schemas/water"},{"type":"null"}],"description":"The body of water the position is on, or null on dry land."},"on_air_at":{"type":"string","format":"date-time","description":"When the confirming beacon landed."},"expires_at":{"type":["string","null"],"format":"date-time","description":"When the op goes off the air unless extended. Null once ended. Derive minutes left from this."},"ended_at":{"type":["string","null"],"format":"date-time","description":"When the op ended, or null while on the air."},"end_reason":{"type":["string","null"],"enum":["qrt","idle_timeout","hard_cap",null],"description":"Why it ended: qrt (a DONE), idle_timeout, or hard_cap. Null while on the air."},"signoff":{"type":["string","null"],"description":"The host's words on their DONE, or null."},"qsos":{"type":"integer","description":"Confirmed QSOs."},"incomplete":{"type":"integer","description":"Entries with no QSL: chasing while on the air, incomplete once ended."},"points":{"type":"integer","description":"The op's score: the sum of its QSOs' points."},"url":{"type":"string","format":"uri","description":"The op page."},"card_image_url":{"type":["string","null"],"format":"uri","description":"The op's share card, digest included, so the URL changes when the card does."},"host":{"$ref":"#/components/schemas/operator_summary","description":"The host's identity."},"entries":{"type":"array","items":{"$ref":"#/components/schemas/qso"},"description":"The logbook. Every chase, oldest first. A chase still awaiting its beacon is not present."},"countries":{"type":"integer","description":"Distinct countries among the chasers, the header's counter."}}},"operator":{"type":"object","description":"One operator: identity, totals, the live op, and recent activity. The JSON of /{callsign}.","required":["callsign","first_heard_at","last_heard_at","totals","on_air","recent_ops","recent_qsos","url","image_url"],"properties":{"callsign":{"type":"string","description":"The base call, the identity behind every SSID."},"first_heard_at":{"type":["string","null"],"format":"date-time","description":"When OTA first heard any station of this call."},"last_heard_at":{"type":["string","null"],"format":"date-time","description":"When OTA last heard any station of this call."},"totals":{"$ref":"#/components/schemas/totals","description":"The header band's numbers, all time."},"on_air":{"oneOf":[{"$ref":"#/components/schemas/op"},{"type":"null"}],"description":"The live op, or null. At most one is possible."},"recent_ops":{"type":"array","items":{"$ref":"#/components/schemas/op"},"description":"The last five completed hosted ops, newest first."},"recent_qsos":{"type":"array","items":{"$ref":"#/components/schemas/qso"},"description":"The last five confirmed QSOs as chaser, newest first."},"url":{"type":"string","format":"uri","description":"The operator page."},"image_url":{"type":["string","null"],"format":"uri","description":"The operator's share card."}}},"plan":{"type":"object","description":"An announcement of a future op. Active plans only.","required":["callsign","station","starts_at","description","url"],"properties":{"callsign":{"type":"string","description":"The planner's base call."},"station":{"type":"string","description":"The station that sent the PLAN."},"starts_at":{"type":"string","format":"date-time","description":"When the op is planned to start, to the minute."},"description":{"type":["string","null"],"description":"How the planner described their plan, or null."},"url":{"type":"string","format":"uri","description":"The planner's operator page; a plan has no permalink of its own."}}},"window":{"type":"object","description":"One of the scoreboard's three time windows, on UTC boundaries.","required":["kind","from","to"],"properties":{"kind":{"type":"string","enum":["month","year","all_time"],"description":"month, year, or all_time."},"from":{"type":["string","null"],"format":"date","description":"The first day of the window, or null for all time."},"to":{"type":["string","null"],"format":"date","description":"The last day of the window, inclusive, or null for all time."}}},"board":{"type":"object","description":"A leaderboard.","required":["slug","name","measure","unit","url"],"properties":{"slug":{"type":"string","description":"The board's URL identity."},"name":{"type":"string","description":"The board's name."},"measure":{"type":"string","description":"The quantity the board counts, as the operator page labels it."},"unit":{"type":"string","description":"The word after a value."},"url":{"type":"string","format":"uri","description":"The board's page."}}},"cell":{"type":"object","description":"One window of one board row. The operator's value, and their rank when on the board.","required":["value","rank"],"properties":{"value":{"type":"integer","description":"The operator's value in the board's own currency."},"rank":{"type":["integer","null"],"description":"The operator's rank, or null when not on the board in this window."}}},"scoreboard_row":{"type":"object","description":"One board the operator holds a value on, across the three windows.","required":["board","cells"],"properties":{"board":{"$ref":"#/components/schemas/board","description":"The board."},"cells":{"type":"array","items":{"$ref":"#/components/schemas/cell"},"description":"One cell per window, in the scoreboard's window order."}}},"scoreboard":{"type":"object","description":"The operator's standings. Every board they hold a value on, three windows each. ie \"The statement\").","required":["callsign","windows","rows"],"properties":{"callsign":{"type":"string","description":"The operator's base call."},"windows":{"type":"array","items":{"$ref":"#/components/schemas/window"},"description":"Current month, current year, all time."},"rows":{"type":"array","items":{"$ref":"#/components/schemas/scoreboard_row"},"description":"One row per board held, in catalog order."}}},"now":{"type":"object","description":"The ops on the air right now, and nothing else. This is the endpoint to poll.).","required":["generated_at","on_air"],"properties":{"generated_at":{"type":"string","format":"date-time","description":"When this answer was computed."},"on_air":{"type":"array","items":{"$ref":"#/components/schemas/op"},"description":"Every op on the air, newest on the air first."}}},"page":{"type":"object","description":"Where a paginated response sits in its collection.","required":["number","size","total","next_url"],"properties":{"number":{"type":"integer","description":"This page, 1-based."},"size":{"type":"integer","description":"Items per page: ?per_page=, default 20, at most 100."},"total":{"type":"integer","description":"Items in the whole filtered collection."},"next_url":{"type":["string","null"],"format":"uri","description":"The next page, or null on the last."}}},"op_page":{"type":"object","description":"A page of ops.","required":["data","page"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/op"},"description":"This page's items."},"page":{"$ref":"#/components/schemas/page","description":"Where this page sits in the collection."}}},"qso_page":{"type":"object","description":"A page of QSOs.","required":["data","page"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/qso"},"description":"This page's items."},"page":{"$ref":"#/components/schemas/page","description":"Where this page sits in the collection."}}},"plan_page":{"type":"object","description":"A page of plans.","required":["data","page"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/plan"},"description":"This page's items."},"page":{"$ref":"#/components/schemas/page","description":"Where this page sits in the collection."}}},"client":{"type":"object","description":"The calling client, as its token identifies it: the answer to \"does my token work\".","required":["name","created_at"],"properties":{"name":{"type":"string","description":"The client's name, as it was minted."},"created_at":{"type":"string","format":"date-time","description":"When the token was minted."}}},"error_body":{"type":"object","description":"What went wrong. `code` is the contract; `message` is prose and may change.","required":["code","message"],"properties":{"code":{"type":"string","enum":["bad_request","unauthorized","token_revoked","not_found","rate_limited"],"description":"One of the documented error codes."},"message":{"type":"string","description":"A sentence for a log."}}},"error":{"type":"object","description":"Every non-2xx the API itself produces has this shape.","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/error_body","description":"The error."}}}},"responses":{"400":{"description":"A parameter is malformed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"},"example":{"error":{"code":"bad_request","message":"A parameter is malformed."}}}}},"401":{"description":"No token, or an unknown one. A token that was valid and no longer is sends token_revoked instead of unauthorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"},"example":{"error":{"code":"unauthorized","message":"No token, or an unknown one. A token that was valid and no longer is sends token_revoked instead of unauthorized."}}}},"headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"Bearer realm=\"aprsota.org\""}}},"404":{"description":"No such operator, op, or QSO.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"},"example":{"error":{"code":"not_found","message":"No such operator, op, or QSO."}}}}},"429":{"description":"Reserved for future use. When implemented, will include Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"},"example":{"error":{"code":"rate_limited","message":"Reserved for future use. When implemented, will include Retry-After."}}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}}}}}}