714 lines
15 KiB
Markdown
714 lines
15 KiB
Markdown
# 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<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
|
|
|
|
```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
|