Checkpoint
This commit is contained in:
713
server/API.md
Normal file
713
server/API.md
Normal file
@@ -0,0 +1,713 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user