4

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.

Heads up

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

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:

  1. 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.
  2. Honor Cache-Control. The response says private, 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.
  3. Send If-None-Match. Every response includes an ETag. Send it back and you get a 304 with 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.

StatuscodeWhen
400bad_requestA parameter is malformed.
401unauthorizedNo token, or an unknown one. A token that was valid and no longer is sends token_revoked instead of unauthorized.
404not_foundNo such operator, op, or QSO.
429rate_limitedReserved 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.

Returns
a client object
Token
required
Errors
401 unauthorized, 429 rate_limited

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.

Returns
the OpenAPI document
Token
not required
Cache
Cache-Control: public, max-age=3600

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.