Developer Documentation
API reference and code examples for building integrations.
TL;DR
Fetch all services in 30 seconds:
curl https://services.aprslive.com/api/v1/registry
No authentication required. Returns JSON with all registered services.
API Overview
The APRS Service Registry provides a RESTful API for querying and managing service registrations. All endpoints return JSON responses.
Base URL
https://services.aprslive.com
Versioning
The API is versioned via URL path. Current version: /api/v1/
Authentication
No authentication required. The API is public and read operations are available to everyone.
Note: Authentication may be added for write operations in the future. Subscribe to the project repository for updates.
Rate Limits
Rate limit: 60 requests per minute per IP address.
Exceeding this limit returns HTTP 429 Too Many Requests with a Retry-After header indicating how many seconds to wait before retrying.
Best practices:
- Cache responses where appropriate
- Avoid polling more frequently than once per minute for real-time applications
- Use webhooks (when available) instead of polling for change detection
Endpoints Summary
| Method | Endpoint | Description | Since |
|---|---|---|---|
| Core Registry (Sprint 1) | |||
| GET | /api/v1/registry |
List all services | v1.13 |
| GET | /api/v1/registry/{callsign} |
Get a specific service | v1.13 |
| POST | /api/v1/registry |
Register or update a service | v1.13 |
| DELETE | /api/v1/registry/{callsign} |
Remove a service (soft-delete) | v1.13 |
| GET | /api/v1/health |
Application health check | v1.13 |
| POST | /api/v1/health-check/{callsign} |
Trigger health check for a service | v1.13 |
| POST | /api/v1/health-check |
Trigger health check for all services | v1.13 |
| Command Catalog (Sprint 2) | |||
| POST | /api/v1/services/{callsign}/commands |
Submit a command for a service | v1.14 |
| GET | /api/v1/services/{callsign}/commands |
List commands for a service | v1.14 |
| Public API Value (Sprint 3) | |||
| GET | /api/v1/registry/{callsign}/health-history |
Paginated health-check probe results | v1.16 |
| GET | /api/v1/registry/{callsign}/uptime |
Uptime percentage + stats over a period | v1.16 |
| GET | /api/v1/registry/{callsign}/badge.svg |
Embeddable shields.io-style SVG badge | v1.16 |
| GET | /api/v1/stats |
Aggregate registry statistics | v1.16 |
| GET | /api/v1/count |
Lightweight polling (ETag/304 support) | v1.16 |
| APRS-Native Discovery (Sprint 4) | |||
| POST | /api/v1/query |
Execute APRS query commands via HTTP | v1.17 |
| Real-Time & Voting (Sprint 6) | |||
| GET | /api/v1/events |
SSE stream for real-time registry events | v1.18 |
| POST | /api/v1/registry/{callsign}/vote |
Cast/toggle a vote (passcode-authenticated) | v1.18 |
| GET | /api/v1/registry/{callsign}/votes |
Vote summary + trust score for a service | v1.18 |
| GET | /api/v1/registry/{callsign}/trust |
Trust score breakdown (uptime/votes/age) | v1.18 |
Error Responses
The API uses standard HTTP status codes:
| Code | Meaning |
|---|---|
200 |
Success |
201 |
Created (for new registrations) |
400 |
Bad request (invalid input) |
404 |
Not found (service doesn't exist) |
500 |
Server error |
Error responses include a JSON body:
{"detail": "Service 'UNKNOWN' not found"}
List All Services
Retrieve all registered APRS services.
Request
GET /api/v1/registry
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
include_deleted |
boolean | false | Include deleted services |
include_all |
boolean | false | Include all services regardless of status |
Response
{
"count": 5,
"timestamp": "2024-01-15T10:30:00Z",
"services": [
{
"callsign": "REPEAT",
"description": "Find the nearest N repeaters to your current location",
"service_website": "http://aprs-repeat.hemna.com",
"software": "APRSD version 3.3.0",
"callsign_owner": "WB4BOR",
"status": "active",
"last_health_check": {
"timestamp": "2024-01-15T10:00:00Z",
"success": true,
"response_time_ms": 1250,
"error": null
}
}
]
}
Code Examples
curl
curl https://services.aprslive.com/api/v1/registry
Python
import requests
response = requests.get("https://services.aprslive.com/api/v1/registry")
data = response.json()
print(f"Found {data['count']} services")
for service in data['services']:
print(f" {service['callsign']}: {service['description']}")
JavaScript
const response = await fetch("https://services.aprslive.com/api/v1/registry");
const data = await response.json();
console.log(`Found ${data.count} services`);
data.services.forEach(service => {
console.log(` ${service.callsign}: ${service.description}`);
});
Go
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type RegistryResponse struct {
Count int `json:"count"`
Services []Service `json:"services"`
}
type Service struct {
Callsign string `json:"callsign"`
Description string `json:"description"`
}
func main() {
resp, err := http.Get("https://services.aprslive.com/api/v1/registry")
if err != nil {
panic(err)
}
defer resp.Body.Close()
var data RegistryResponse
json.NewDecoder(resp.Body).Decode(&data)
fmt.Printf("Found %d services\n", data.Count)
for _, svc := range data.Services {
fmt.Printf(" %s: %s\n", svc.Callsign, svc.Description)
}
}
Get a Specific Service
Retrieve details for a single service by callsign.
Request
GET /api/v1/registry/{callsign}
Response
{
"callsign": "REPEAT",
"description": "Find the nearest N repeaters to your current location",
"service_website": "http://aprs-repeat.hemna.com",
"software": "APRSD version 3.3.0",
"callsign_owner": "WB4BOR",
"status": "active",
"last_health_check": {
"timestamp": "2024-01-15T10:00:00Z",
"success": true,
"response_time_ms": 1250,
"error": null
}
}
Code Examples
curl
curl https://services.aprslive.com/api/v1/registry/REPEAT
Python
import requests
callsign = "REPEAT"
response = requests.get(f"https://services.aprslive.com/api/v1/registry/{callsign}")
if response.status_code == 200:
service = response.json()
print(f"{service['callsign']}: {service['description']}")
elif response.status_code == 404:
print(f"Service {callsign} not found")
JavaScript
const callsign = "REPEAT";
const response = await fetch(`https://services.aprslive.com/api/v1/registry/${callsign}`);
if (response.ok) {
const service = await response.json();
console.log(`${service.callsign}: ${service.description}`);
} else if (response.status === 404) {
console.log(`Service ${callsign} not found`);
}
Go
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
callsign := "REPEAT"
url := fmt.Sprintf("https://services.aprslive.com/api/v1/registry/%s", callsign)
resp, err := http.Get(url)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
var service map[string]interface{}
json.NewDecoder(resp.Body).Decode(&service)
fmt.Printf("%s: %s\n", service["callsign"], service["description"])
} else if resp.StatusCode == 404 {
fmt.Printf("Service %s not found\n", callsign)
}
}
Register a Service
Register a new service or update an existing one.
Request
POST /api/v1/registry
Request Body
{
"callsign": "MYCALL",
"description": "My APRS service description",
"service_website": "http://example.com",
"software": "APRSD 3.3.0",
"callsign_owner": "N0CALL"
}
Code Examples
curl
curl -X POST https://services.aprslive.com/api/v1/registry \
-H "Content-Type: application/json" \
-d '{
"callsign": "MYCALL",
"description": "My APRS service",
"service_website": "http://example.com",
"software": "APRSD 3.3.0"
}'
Python
import requests
data = {
"callsign": "MYCALL",
"description": "My APRS service",
"service_website": "http://example.com",
"software": "APRSD 3.3.0",
}
response = requests.post(
"https://services.aprslive.com/api/v1/registry",
json=data
)
print(response.json())
JavaScript
const data = {
callsign: "MYCALL",
description: "My APRS service",
service_website: "http://example.com",
software: "APRSD 3.3.0",
};
const response = await fetch("https://services.aprslive.com/api/v1/registry", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
console.log(await response.json());
Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]string{
"callsign": "MYCALL",
"description": "My APRS service",
"service_website": "http://example.com",
"software": "APRSD 3.3.0",
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"https://services.aprslive.com/api/v1/registry",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println(result)
}
Trigger Health Check
Manually trigger a health check for a specific service.
Request
POST /api/v1/health-check/{callsign}
Response
{
"status": "ok",
"callsign": "REPEAT",
"health_check": {
"timestamp": "2024-01-15T10:30:00Z",
"success": true,
"response_time_ms": 1250,
"error": null
}
}
Code Examples
curl
curl -X POST https://services.aprslive.com/api/v1/health-check/REPEAT
Python
import requests
callsign = "REPEAT"
response = requests.post(f"https://services.aprslive.com/api/v1/health-check/{callsign}")
result = response.json()
if result["health_check"]["success"]:
print(f"Health check passed in {result['health_check']['response_time_ms']}ms")
else:
print(f"Health check failed: {result['health_check']['error']}")
Command Catalog (Sprint 2)
Services can register their supported APRS commands so users know what messages to send.
Submit a Command
POST /api/v1/services/{callsign}/commands
Requires APRS passcode authentication. Commands are queued for admin approval.
curl -X POST https://services.aprslive.com/api/v1/services/REPEAT/commands \
-H "Content-Type: application/json" \
-d '{
"passcode": "12345",
"name": "find",
"description": "Find nearest repeaters to your location",
"example": "find 5",
"response_format": "Multi-message reply with repeater info"
}'
List Commands
GET /api/v1/services/{callsign}/commands
curl https://services.aprslive.com/api/v1/services/REPEAT/commands
Response
{
"callsign": "REPEAT",
"commands": [
{
"name": "find",
"description": "Find nearest repeaters to your location",
"example": "find 5",
"response_format": "Multi-message reply with repeater info"
},
{
"name": "help",
"description": "List available commands"
}
]
}
Health History & Uptime (Sprint 3)
Access historical health-check data, uptime percentages, and embeddable status badges.
Health History
GET /api/v1/registry/{callsign}/health-history
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
integer | 100 | Max records to return (max 1000) |
since |
ISO timestamp | — | Only return checks after this time |
curl "https://services.aprslive.com/api/v1/registry/WXBOT/health-history?limit=50&since=2026-08-01T00:00:00Z"
Uptime Statistics
GET /api/v1/registry/{callsign}/uptime
| Parameter | Type | Default | Description |
|---|---|---|---|
period |
string | 7d | Time period: 1d, 7d, 30d, 90d |
curl https://services.aprslive.com/api/v1/registry/WXBOT/uptime?period=30d
Response
{
"callsign": "WXBOT",
"period": "7d",
"total_checks": 168,
"successful_checks": 165,
"failed_checks": 3,
"uptime_percent": 98.21,
"avg_response_time_ms": 142.5
}
Status Badge
GET /api/v1/registry/{callsign}/badge.svg
Returns an SVG badge (shields.io style) with color-coded uptime. Cached for 5 minutes.
<img src="https://services.aprslive.com/api/v1/registry/WXBOT/badge.svg" alt="uptime">
Registry Statistics
GET /api/v1/stats
Aggregate statistics: total services, active/down counts, overall uptime.
curl https://services.aprslive.com/api/v1/stats
Lightweight Count (Polling)
GET /api/v1/count
Returns service count with ETag support. Use If-None-Match header for conditional GETs (returns 304 when unchanged). Rate limit: 120/minute.
# First request
curl -i https://services.aprslive.com/api/v1/count
# → ETag: "abc123"
# Subsequent poll (returns 304 if unchanged)
curl -H 'If-None-Match: "abc123"' https://services.aprslive.com/api/v1/count
APRS-Native Discovery (Sprint 4)
Radio-only users can query the registry by messaging RGSTRY over APRS — no internet required. The same command interface is also available via HTTP for web/mobile clients.
Over APRS Radio
Send a message to RGSTRY on the APRS network:
| You Send | RGSTRY Replies |
|---|---|
? or help |
Service count + available commands + web URL |
list |
Paginated comma-separated callsigns (up to 5 pages) |
list 2 |
Just page 2 |
list weather |
Only services in the "weather" category |
find <keyword> |
Matching callsigns |
info REPEAT |
Description, owner, and status |
cmds REPEAT |
Command catalog for that service |
Constraints: All responses ≤67 chars. Rate-limited to 1 query per 60s per station. SSID-agnostic (RGSTRY and RGSTRY-10 both work).
Via HTTP API
POST /api/v1/query
Execute the same APRS commands via HTTP (for web UI, mobile apps, or testing).
Request Body
{
"command": "find weather",
"from_call": "WEBCLIENT"
}
Response
{
"command": "find weather",
"from_call": "WEBCLIENT",
"responses": ["Found:WXBOT,WXNOW,WXYO"],
"count": 1
}
Code Examples
curl
curl -X POST https://services.aprslive.com/api/v1/query \
-H "Content-Type: application/json" \
-d '{"command": "help"}'
Python
import requests
response = requests.post(
"https://services.aprslive.com/api/v1/query",
json={"command": "list weather"}
)
data = response.json()
for msg in data["responses"]:
print(msg)
JavaScript
const response = await fetch("https://services.aprslive.com/api/v1/query", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ command: "info WXBOT" }),
});
const data = await response.json();
data.responses.forEach(msg => console.log(msg));
Real-Time Events & Voting (Sprint 6)
SSE Event Stream
Connect to receive real-time push notifications:
const es = new EventSource('/api/v1/events');
es.addEventListener('service.status_changed', (e) => {
const data = JSON.parse(e.data);
console.log(`${data.data.callsign}: ${data.data.old_status} → ${data.data.new_status}`);
});
es.addEventListener('vote.cast', (e) => {
const data = JSON.parse(e.data);
console.log(`${data.data.voter} voted on ${data.data.callsign}`);
});
Event types: service.registered, service.updated,
service.deleted, service.status_changed,
health_check.completed, vote.cast.
30-second keepalive heartbeats. Max 10 connections per IP.
Community Voting
Vote on services using your APRS passcode:
# Upvote a service
curl -X POST /api/v1/registry/WXBOT/vote \
-H "Content-Type: application/json" \
-d '{"callsign": "KM6LYW", "passcode": 12345, "vote": "up"}'
# Response:
# {"callsign":"WXBOT","voter":"KM6LYW","action":"created",
# "current_vote":1,"upvotes":1,"downvotes":0,"score":1,"voter_count":1}
# Vote same direction again to toggle off (remove vote)
# Switch "up" to "down" to change direction
Trust Score
Trust score (0–100) combines uptime, community votes, and service age:
GET /api/v1/registry/WXBOT/trust
# {"callsign":"WXBOT","trust_score":65.3,
# "components":{"uptime_weight":50,"vote_weight":30,"age_weight":20},
# "votes":{"upvotes":3,"downvotes":0,"score":3,"voter_count":3}}
Rate Limits
| Action | Limit | Scope |
|---|---|---|
| Votes | 10/hour | Per callsign |
| Vote endpoint | 30/min | Per IP |
| SSE connections | 10 concurrent | Per IP |
Full API Reference
For the complete API specification with all parameters and response schemas, see:
- Swagger UI — Interactive API explorer
- ReDoc — Clean API documentation