# HAMView API Reference Version: 1.0 Base URL: `/api/v1` Content-Type: `application/json` ## Table of Contents 1. [Authentication](#authentication) 2. [Error Handling](#error-handling) 3. [Endpoints](#endpoints) - [Radios](#radios) - [MeshCore](#meshcore) - [APRS](#aprs) 4. [Data Models](#data-models) 5. [Examples](#examples) --- ## Authentication Currently, the API does not require authentication. All endpoints are publicly accessible. --- ## Error Handling All error responses follow this format: ```json { "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` ```typescript Radio[] ``` **Example:** ```bash curl http://localhost:8073/api/v1/radios ``` ```json [ { "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` ```typescript Radio[] ``` **Example:** ```bash curl http://localhost:8073/api/v1/radios/meshcore ``` --- ### MeshCore #### Get MeshCore Statistics ``` GET /api/v1/meshcore ``` Returns aggregated network statistics. **Response:** `200 OK` ```typescript { messages: number; nodes: number; receivers: number; packets: { timestamps: number[]; packets: number[]; }; } ``` **Example:** ```bash curl http://localhost:8073/api/v1/meshcore ``` ```json { "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` ```typescript { id: number; name: string; secret: string; }[] ``` **Example:** ```bash curl http://localhost:8073/api/v1/meshcore/groups ``` ```json [ { "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` ```typescript { 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:** ```bash curl http://localhost:8073/api/v1/meshcore/nodes curl http://localhost:8073/api/v1/meshcore/nodes?type=chat ``` ```json [ { "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` ```typescript { node: MeshCoreNode; nodes: MeshCoreNode[]; // Sorted by distance, with distance field populated } ``` **Example:** ```bash curl "http://localhost:8073/api/v1/meshcore/nodes/close-to/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef?radius=50000" ``` ```json { "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` ```typescript { 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:** ```bash # 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 ``` ```json [ { "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` ```typescript { 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:** ```bash # 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 ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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 ```typescript interface MeshCoreGroup { id: number; name: string; // Max 32 characters secret: string; // 32-char hex } ``` ### MeshCoreStats ```typescript interface MeshCoreStats { messages: number; nodes: number; receivers: number; packets: { timestamps: number[]; // Unix timestamps packets: number[]; // Packet counts }; } ``` --- ## Examples ### JavaScript/TypeScript Client ```typescript // Using fetch API const API_BASE = 'http://localhost:8073/api/v1'; // Get all radios async function getRadios(): Promise { 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 { 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 ```python 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 ```go 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