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