Developer Documentation
API reference and code examples for building integrations.
TL;DR
Fetch all services in 30 seconds:
curl https://aprs.hemna.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://aprs.hemna.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 |
|---|---|---|
| GET | /api/v1/registry |
List all services |
| GET | /api/v1/registry/{callsign} |
Get a specific service |
| POST | /api/v1/registry |
Register or update a service |
| DELETE | /api/v1/registry/{callsign} |
Remove a service |
| POST | /api/v1/health-check/{callsign} |
Trigger health check for a service |
| POST | /api/v1/health-check |
Trigger health check for all services |
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://aprs.hemna.com/api/v1/registry
Python
import requests
response = requests.get("https://aprs.hemna.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://aprs.hemna.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://aprs.hemna.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://aprs.hemna.com/api/v1/registry/REPEAT
Python
import requests
callsign = "REPEAT"
response = requests.get(f"https://aprs.hemna.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://aprs.hemna.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://aprs.hemna.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://aprs.hemna.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://aprs.hemna.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://aprs.hemna.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://aprs.hemna.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://aprs.hemna.com/api/v1/health-check/REPEAT
Python
import requests
callsign = "REPEAT"
response = requests.post(f"https://aprs.hemna.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']}")
Full API Reference
For the complete API specification with all parameters and response schemas, see:
- Swagger UI — Interactive API explorer
- ReDoc — Clean API documentation