The API
The site provides a read-only JSON API for developers building APRS OTA into other apps and tools. The API is read-only because the only way to get anything onto this site is to send APRS messages. You can’t use the API to host an op or complete a QSL.
The HTTP API requires authentication to reduce the chances for abuse. There is no fee to use it, all you have to do is email me and I’ll issue a token.
I’ve also made a limited subset of data available via the radio with
alternative machine forms of several commands, including
CQ and CHASERS. These commands send messages back to you in a stable,
easy to parse form. Because APRS is a low bandwith protocol, these commands
carry much less data than the HTTP API. These bot commands require no authentication
but they carry the same rate limits as their designed-for-humans counterparts.
Authentication Tokens
To get an authentication token for the HTTP API, email me with a short description of your project, app, or use case. As long as you are aligned with the principles of ham radio (non-commercial technical experimentation and international goodwill), I’ll be happy to issue you a token. I’m also very lenient with the non-commercial part. It’s no problem if you have an app that you charge money for and want to use the API.
An authentication token will be issued for your project, not for each user of your project.
A token looks like aprsota_ followed by 32 random characters. Treat it like
any authentication secret. Don’t check it into your source code repository,
and don’t embed it in a web page (see “No Cross-Origin Resource Sharing” below).
If you need to rotate your token or it has been compromised, email and ask for a second one. If it’s just a rotation, say when you want the rotation to happen, multiple tokens can be active at the same time so you can get a seamless cutover.
Two headers
Every request you make to the API must carry the authentication token in the
Authrotization HTTP header:
GET /api/v1/now HTTP/1.1
Host: aprsota.org
Authorization: Bearer aprsota_7GxQ...
OTA-User: 2f1c9d3e-8a4b-4c7d-9e1f-3b5a7c9d1e2f
Don’t ever use the token in the URL. It won’t work, and it will be exposed in access logs, browser history, and referrer headers.
The second header, OTA-User, is optional and tells one of your
users apart from another in the site logs, so that if something misbehaves
we can see whether it is one user or all of them. Send an opaque string of at
most 64 characters. For mobile apps, a random UUID created the
first time your app runs is the ideal solution.
Do not put anything in the OTA-User that identifies a person.
I do not want to receive or store any personal information. Please do not use
callsigns, email addresses, user ids, device identifiers, IP addresses, or names.
OTA-User is optional because some use cases and projects will
not have or know who the user is. For example, if you have a web project that
uses the API to generate an APRS OTA badge, you probably have no way to identify
every user. It’s better to leave it blank than to put the IP address or other
identifying information in the field.
No Cross-Origin Resource Sharing
Cross-Origin Resource Sharing (CORS) is a browser-enforced security measure
that says whether browser JavaScript code from one domain can safely interact
with a web API on a different domain. The APRS OTA API sends
no Access-Control-Allow-Origin header, so JavaScript on a web page cannot
call it directly. A web page that called this API
would have to ship its authentication token in the page, where anybody can read it.
This API is for servers and native apps. If you want a badge or a widget on a website, your
server will need to make the call, draw the image, and then serve it to your
clients.
Conventions
- Every path lives under
/api/v1/. API paths rhyme with the URLs on the site./K0TFU/ops/12shows the object at/api/v1/operators/K0TFU/ops/12. - Callsigns in paths are base calls (no SSIDs) and uppercase. Send
k0tfu-7and you get a301toK0TFU. - Times are ISO 8601 in UTC with a
Zsuffix and include seconds. - Every field is always present. Fields with no values are
null, never missing, so you can tell “no grid” from “this server does not send grids”. - Objects that have a page carry their
url, and objects that have a share card carry its URL. Use these instead of building an aprsota.org URL yourself. - Positions are latitude and longitude as signed decimal degrees at
six decimals. They are
nullwhen the stored pair is not a place. /api/v1only ever grows. New fields and endpoints may appear, but a field never disappears, changes type, or changes meaning. If we need those kinds of changes in the future, they will be under/api/v2.
Caching and polling
/api/v1/now is the endpoint most apps will poll, and it’s built to handle
heavy load. You don’t have to baby this endpoint, or any other endpoint, but you
can do three things to be a polite client:
- Poll no more than once a minute. That is the cadence this site’s own pages refresh at. APRS messages are slow enough that polling more often than 1 minute doesn’t give much benefit.
- Honor
Cache-Control. The response saysprivate, max-age=30. iOS and Android HTTP stacks honor it without you doing anything, so a user who reopens a screen or mashes your refresh button only sends one network request. - Send
If-None-Match. Every response includes anETag. Send it back and you get a304with no body when nothing changed.
There is no API throttling today. When it gets implemented, if your calls to the
API are throttled, the answer will be 429 with a Retry-After header and the
rate_limited code below.
Errors
Every error the API produces looks the same:
{ "error": { "code": "not_found", "message": "OTA has not heard W1XYZ yet." } }
code containts a stable error label and message is prose (which may change) for the log.
| Status | code | When |
|---|---|---|
| 400 | bad_request | A parameter is malformed. |
| 401 | unauthorized | No token, or an unknown one. A token that was valid and no longer is sends token_revoked instead of unauthorized. |
| 404 | not_found | No such operator, op, or QSO. |
| 429 | rate_limited | Reserved for future use. When implemented, will include Retry-After. |
A 50x response is not part of the API contract. Ruby on Rails produces these responses
and if you get one, it means I broke something in the API. Let me know and I’ll get it
sorted.
Checking a token
GET /api/v1/client
Does my token work?
Answers with the client's name and token creation date. This is an easy way to validate your authentication token works.
Example
{
"name": "APRS Connect",
"created_at": "2026-09-18T17:00:00Z"
}The OpenAPI document
The whole API is described by an OpenAPI 3.2 document at
/api/v1/openapi.json. You don’t need an authentication
token to access this endpoint, so you can import it into your tools before
you have one.
The document, the serializers, and every page in this section are generated from a catalog in the source code, so they cannot disagree with each other. If you find an API response that’s different from this documentation, that is a bug, and we want to know.
GET /api/v1/openapi.json
The OpenAPI document for this API.
Unauthenticated, so a developer can import it before they have a token. It describes all public endpoints.
Please avoid
If you snoop around you may find two JSON files under this site’s root:
/on-air.json and /callsigns.json. They are part of the site’s plumbing
for the masthead “on the air” live display and the callsign search field.
They may carry HTML markup, and can change or disappear without notice.
Avoid using these and stick with the API.