Files
hamview/server/API.md
maze 13afa08e8a
Some checks failed
Test and build / Test and lint (push) Failing after 36s
Test and build / Build collector (push) Failing after 43s
Test and build / Build receiver (push) Failing after 42s
Checkpoint
2026-03-05 15:38:18 +01:00

15 KiB

HAMView API Reference

Version: 1.0 Base URL: /api/v1 Content-Type: application/json

Table of Contents

  1. Authentication
  2. Error Handling
  3. Endpoints
  4. Data Models
  5. Examples

Authentication

Currently, the API does not require authentication. All endpoints are publicly accessible.


Error Handling

All error responses follow this format:

{
  "error": "Human-readable error message"
}

HTTP Status Codes

Code Description
200 Success
400 Bad Request - Invalid parameters
404 Not Found - Resource does not exist
500 Internal Server Error

Endpoints

Radios

List All Radios

GET /api/v1/radios

Returns a list of all radio receivers/stations.

Response: 200 OK

Radio[]

Example:

curl http://localhost:8073/api/v1/radios
[
  {
    "id": 1,
    "name": "Station-Alpha",
    "is_online": true,
    "manufacturer": "Heltec",
    "protocol": "meshcore",
    "frequency": 868.1
  }
]

List Radios by Protocol

GET /api/v1/radios/:protocol

Returns radios filtered by protocol.

Parameters:

Name Type In Description
protocol string path Protocol name (e.g., "meshcore", "aprs")

Response: 200 OK

Radio[]

Example:

curl http://localhost:8073/api/v1/radios/meshcore

MeshCore

Get MeshCore Statistics

GET /api/v1/meshcore

Returns aggregated network statistics.

Response: 200 OK

{
  messages: number;
  nodes: number;
  receivers: number;
  packets: {
    timestamps: number[];
    packets: number[];
  };
}

Example:

curl http://localhost:8073/api/v1/meshcore
{
  "messages": 150234,
  "nodes": 127,
  "receivers": 8,
  "packets": {
    "timestamps": [1709650800, 1709654400],
    "packets": [142, 203]
  }
}

List MeshCore Groups

GET /api/v1/meshcore/groups

Returns public MeshCore groups/channels.

Response: 200 OK

{
  id: number;
  name: string;
  secret: string;
}[]

Example:

curl http://localhost:8073/api/v1/meshcore/groups
[
  {
    "id": 5,
    "name": "General Chat",
    "secret": "0123456789abcdef0123456789abcdef"
  }
]

List MeshCore Nodes

GET /api/v1/meshcore/nodes

Returns MeshCore network nodes.

Query Parameters:

Name Type Required Description
type string No Node type: "chat", "room", "sensor", "repeater"

Response: 200 OK

{
  id: number;
  packet: MeshCorePacket[];
  name: string;
  type: number;
  prefix: string;
  public_key: string;
  first_heard_at: string;
  last_heard_at: string;
  last_latitude: number | null;
  last_longitude: number | null;
  distance?: number;
}[]

Example:

curl http://localhost:8073/api/v1/meshcore/nodes
curl http://localhost:8073/api/v1/meshcore/nodes?type=chat
[
  {
    "id": 42,
    "packet": [],
    "name": "NODE-CHARLIE",
    "type": 0,
    "prefix": "mc",
    "public_key": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
    "first_heard_at": "2026-01-15T08:00:00Z",
    "last_heard_at": "2026-03-05T14:25:00Z",
    "last_latitude": 52.3667,
    "last_longitude": 4.8945
  }
]

Find Nodes Near Location

GET /api/v1/meshcore/nodes/close-to/:publickey

Returns nodes within a specified radius of a reference node.

Path Parameters:

Name Type Description
publickey string 64-character hex-encoded Ed25519 public key

Query Parameters:

Name Type Required Default Description
radius number No 25000 Search radius in meters

Response: 200 OK

{
  node: MeshCoreNode;
  nodes: MeshCoreNode[];  // Sorted by distance, with distance field populated
}

Example:

curl "http://localhost:8073/api/v1/meshcore/nodes/close-to/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef?radius=50000"
{
  "node": {
    "id": 42,
    "name": "NODE-CHARLIE",
    "public_key": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
    "last_latitude": 52.3667,
    "last_longitude": 4.8945,
    "distance": 0
  },
  "nodes": [
    {
      "id": 43,
      "name": "NODE-DELTA",
      "last_latitude": 52.3700,
      "last_longitude": 4.9000,
      "distance": 450.5
    }
  ]
}

List MeshCore Packets

GET /api/v1/meshcore/packets

Returns MeshCore packets based on filter criteria.

Query Parameters (mutually exclusive):

Name Type Required Description
hash string No 16-character hex hash
type number No Payload type (0-255)
channel_hash string No 2-character channel hash (requires type)

Response: 200 OK

{
  id: number;
  radio_id: number;
  radio: Radio | null;
  snr: number;
  rssi: number;
  version: number;
  route_type: number;
  payload_type: number;
  hash: string;
  path: string;        // Base64
  payload: string;     // Base64
  raw: string;         // Base64
  parsed: object | null;
  channel_hash: string;
  received_at: string;
}[]

Payload Types:

Value Description
0 Ping
1 Node announcement
2 Direct text message
3 Group text message
4 Position update

Examples:

# Get 100 most recent packets
curl http://localhost:8073/api/v1/meshcore/packets

# Get packets by hash
curl http://localhost:8073/api/v1/meshcore/packets?hash=a1b2c3d4e5f67890

# Get packets by type
curl http://localhost:8073/api/v1/meshcore/packets?type=3

# Get group messages for a channel
curl http://localhost:8073/api/v1/meshcore/packets?type=3&channel_hash=ab
[
  {
    "id": 12345,
    "radio_id": 1,
    "snr": 8.5,
    "rssi": -95,
    "version": 1,
    "route_type": 0,
    "payload_type": 3,
    "hash": "a1b2c3d4e5f67890",
    "path": "AQIDBA==",
    "payload": "SGVsbG8gV29ybGQ=",
    "raw": "AQIDBAUGBwg=",
    "parsed": {"text": "Hello World"},
    "channel_hash": "ab",
    "received_at": "2026-03-05T14:30:00Z"
  }
]

APRS

List APRS Packets

GET /api/v1/aprs/packets

Returns APRS packets based on filter criteria.

Query Parameters (evaluated in order):

Name Type Required Default Description
src string No - Source callsign (case-insensitive)
dst string No - Destination callsign (case-insensitive)
limit number No 100 Maximum number of packets when no src/dst filter is used

Response: 200 OK

{
  id: number;
  radio_id: number;
  radio: Radio;
  src: string;
  dst: string;
  path: string;
  comment: string;
  latitude: number | null;
  longitude: number | null;
  symbol: string;
  raw: string;
  received_at: string;
}[]

Examples:

# Get 100 most recent packets
curl http://localhost:8073/api/v1/aprs/packets

# Get packets by source callsign
curl http://localhost:8073/api/v1/aprs/packets?src=OE1ABC

# Get packets by destination callsign
curl http://localhost:8073/api/v1/aprs/packets?dst=APRS

# Get recent packets with explicit limit
curl http://localhost:8073/api/v1/aprs/packets?limit=200

Data Models

Radio

interface Radio {
  id: number;                      // Unique identifier
  name: string;                    // Station name
  is_online: boolean;              // Online status
  manufacturer: string;            // Hardware manufacturer
  device: string | null;           // Device model
  firmware_version: string | null; // Firmware version
  firmware_date: string | null;    // ISO 8601 timestamp
  antenna: string | null;          // Antenna description
  modulation: string;              // e.g., "LoRa"
  protocol: string;                // e.g., "meshcore", "aprs"
  latitude: number | null;         // Decimal degrees
  longitude: number | null;        // Decimal degrees
  altitude: number | null;         // Meters
  frequency: number;               // Hz
  bandwidth: number;               // Hz
  power: number | null;            // dBm
  gain: number | null;             // dBi
  lora_sf: number | null;          // LoRa spreading factor (7-12)
  lora_cr: number | null;          // LoRa coding rate (5-8)
  extra: object | null;            // Additional metadata
  created_at: string;              // ISO 8601 timestamp
  updated_at: string;              // ISO 8601 timestamp
}

MeshCorePacket

interface MeshCorePacket {
  id: number;
  radio_id: number;
  radio: Radio | null;
  snr: number;                     // Signal-to-noise ratio (dB)
  rssi: number;                    // Received signal strength (dBm)
  version: number;
  route_type: number;
  payload_type: number;
  hash: string;                    // 16-char hex
  path: string;                    // Base64-encoded
  payload: string;                 // Base64-encoded
  raw: string;                     // Base64-encoded
  parsed: object | null;           // Depends on payload_type
  channel_hash: string;            // 2-char hex
  received_at: string;             // ISO 8601 timestamp
}

APRSPacket

interface APRSPacket {
  id: number;
  radio_id: number;
  radio: Radio;
  src: string;                     // Source callsign
  dst: string;                     // Destination callsign
  path: string;                    // Digipeater path
  comment: string;
  latitude: number | null;
  longitude: number | null;
  symbol: string;                  // APRS symbol table + code
  raw: string;                     // Raw APRS packet
  received_at: string;             // ISO 8601 timestamp
}

MeshCoreNode

interface MeshCoreNode {
  id: number;
  packet: MeshCorePacket[];
  name: string;
  type: number;                    // 0=repeater, 1=chat, 2=room, 3=sensor
  prefix: string;                  // 2-char network prefix
  public_key: string;              // 64-char hex Ed25519 public key
  first_heard_at: string;          // ISO 8601 timestamp
  last_heard_at: string;           // ISO 8601 timestamp
  last_latitude: number | null;
  last_longitude: number | null;
  distance?: number;               // Meters (proximity queries only)
}

MeshCoreGroup

interface MeshCoreGroup {
  id: number;
  name: string;                    // Max 32 characters
  secret: string;                  // 32-char hex
}

MeshCoreStats

interface MeshCoreStats {
  messages: number;
  nodes: number;
  receivers: number;
  packets: {
    timestamps: number[];          // Unix timestamps
    packets: number[];             // Packet counts
  };
}

Examples

JavaScript/TypeScript Client

// Using fetch API
const API_BASE = 'http://localhost:8073/api/v1';

// Get all radios
async function getRadios(): Promise<Radio[]> {
  const response = await fetch(`${API_BASE}/radios`);
  if (!response.ok) throw new Error(await response.text());
  return response.json();
}

// Get MeshCore nodes by type
async function getMeshCoreNodes(type?: string): Promise<MeshCoreNode[]> {
  const url = type
    ? `${API_BASE}/meshcore/nodes?type=${type}`
    : `${API_BASE}/meshcore/nodes`;
  const response = await fetch(url);
  if (!response.ok) throw new Error(await response.text());
  return response.json();
}

// Find nearby nodes
async function getNodesNearby(publicKey: string, radius: number = 25000) {
  const response = await fetch(
    `${API_BASE}/meshcore/nodes/close-to/${publicKey}?radius=${radius}`
  );
  if (!response.ok) throw new Error(await response.text());
  return response.json();
}

Python Client

import requests
from typing import List, Optional, Dict, Any

API_BASE = 'http://localhost:8073/api/v1'

def get_radios(protocol: Optional[str] = None) -> List[Dict[str, Any]]:
    """Get all radios or filter by protocol."""
    url = f"{API_BASE}/radios/{protocol}" if protocol else f"{API_BASE}/radios"
    response = requests.get(url)
    response.raise_for_status()
    return response.json()

def get_meshcore_packets(
    hash: Optional[str] = None,
    type: Optional[int] = None,
    channel_hash: Optional[str] = None
) -> List[Dict[str, Any]]:
    """Get MeshCore packets with optional filters."""
    params = {}
    if hash:
        params['hash'] = hash
    if type is not None:
        params['type'] = type
    if channel_hash:
        params['channel_hash'] = channel_hash

    response = requests.get(f"{API_BASE}/meshcore/packets", params=params)
    response.raise_for_status()
    return response.json()

def get_meshcore_stats() -> Dict[str, Any]:
    """Get MeshCore network statistics."""
    response = requests.get(f"{API_BASE}/meshcore")
    response.raise_for_status()
    return response.json()

Go Client

package hamview

import (
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
)

const APIBase = "http://localhost:8073/api/v1"

type Client struct {
    BaseURL string
    HTTP    *http.Client
}

func NewClient() *Client {
    return &Client{
        BaseURL: APIBase,
        HTTP:    &http.Client{},
    }
}

func (c *Client) GetRadios() ([]Radio, error) {
    var radios []Radio
    err := c.get("/radios", &radios)
    return radios, err
}

func (c *Client) GetMeshCoreNodes(nodeType string) ([]MeshCoreNode, error) {
    path := "/meshcore/nodes"
    if nodeType != "" {
        path += "?type=" + url.QueryEscape(nodeType)
    }
    var nodes []MeshCoreNode
    err := c.get(path, &nodes)
    return nodes, err
}

func (c *Client) get(path string, result interface{}) error {
    resp, err := c.HTTP.Get(c.BaseURL + path)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("HTTP %d", resp.StatusCode)
    }

    return json.NewDecoder(resp.Body).Decode(result)
}

Rate Limiting

Currently no rate limiting is implemented.

Versioning

API version is specified in the URL path (/api/v1). Breaking changes will increment the version number.

Support

For issues or questions, please refer to the project documentation at https://git.maze.io/ham/hamview