REST API Reference
foxd exposes a REST API under the /api prefix. All responses are JSON.
Base URL
The API is available at:
http://<host>:<port>/apiDefault: http://localhost:8080/api
Authentication
The current version of foxd does not require authentication. API access is controlled by network-level security (bind address configuration). It is recommended to bind to 127.0.0.1 or use a firewall if exposing the API.
Response Format
All successful responses return JSON with appropriate HTTP status codes:
200 OK- Successful GET, POST, or PUT request201 Created- Resource created successfully (not currently used)204 No Content- Successful DELETE (not currently used)400 Bad Request- Invalid request data404 Not Found- Resource not found500 Internal Server Error- Server error
Error Response
All errors return a JSON object with an error field:
{
"error": "Description of the error",
"details": "Optional additional details"
}Health
GET /api/health
Returns daemon status and system metrics.
Response:
{
"status": "ok",
"service": "foxd",
"uptime_seconds": 3600,
"system": {
"cpu_usage_percent": 12,
"memory_usage_percent": 45,
"total_memory_mb": 1024,
"used_memory_mb": 460
}
}Status Codes:
200 OK- Always returns 200 if daemon is running
Devices
GET /api/devices
List all discovered devices on the network.
Response:
{
"devices": [
{
"id": 1,
"mac_address": "aa:bb:cc:dd:ee:ff",
"ip_address": "192.168.1.42",
"hostname": "my-laptop",
"nickname": null,
"vendor": "Apple Inc.",
"status": "online",
"first_seen": "2025-01-15T10:00:00Z",
"last_seen": "2025-01-15T12:30:00Z"
}
],
"count": 1
}Device Status Values:
online- Device has been seen recentlyoffline- Device has not been seen within timeout periodunknown- Initial state before first status update
Status Codes:
200 OK- Success500 Internal Server Error- Database error
GET /api/devices/{mac}
Get a single device by MAC address.
Parameters:
mac(path) - MAC address in formataa:bb:cc:dd:ee:ff(colons required)
Response:
{
"id": 1,
"mac_address": "aa:bb:cc:dd:ee:ff",
"ip_address": "192.168.1.42",
"hostname": "my-laptop",
"nickname": "Dad's Laptop",
"vendor": "Apple Inc.",
"status": "online",
"first_seen": "2025-01-15T10:00:00Z",
"last_seen": "2025-01-15T12:30:00Z"
}Status Codes:
200 OK- Device found404 Not Found- Device does not exist500 Internal Server Error- Database error
Example:
curl http://localhost:8080/api/devices/aa:bb:cc:dd:ee:ffPOST /api/devices/{mac}/nickname
Set or clear a device nickname.
Parameters:
mac(path) - MAC address in formataa:bb:cc:dd:ee:ff
Request Body:
{
"nickname": "Dad's Laptop"
}To clear a nickname, pass null:
{
"nickname": null
}Response:
Returns the updated device object:
{
"id": 1,
"mac_address": "aa:bb:cc:dd:ee:ff",
"ip_address": "192.168.1.42",
"hostname": "my-laptop",
"nickname": "Dad's Laptop",
"vendor": "Apple Inc.",
"status": "online",
"first_seen": "2025-01-15T10:00:00Z",
"last_seen": "2025-01-15T12:30:00Z"
}Status Codes:
200 OK- Nickname updated404 Not Found- Device does not exist500 Internal Server Error- Database error
Example:
curl -X POST http://localhost:8080/api/devices/aa:bb:cc:dd:ee:ff/nickname \
-H "Content-Type: application/json" \
-d '{"nickname": "Dad'\''s Laptop"}'Rules
Rules define when and how notifications are triggered based on device events.
GET /api/rules
List all notification rules.
Response:
{
"rules": [
{
"id": 1,
"name": "Alert on new devices",
"description": "Notify when an unknown device joins the network",
"trigger_type": "new_device",
"mac_filter": null,
"enabled": true,
"notification_channels": ["telegram_123456789"],
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-15T10:00:00Z"
}
],
"count": 1
}Trigger Types:
new_device- Device seen for the first timedevice_connected- Device comes onlinedevice_disconnected- Device goes offlinedevice_status_change- Any status change (online ↔ offline)
Status Codes:
200 OK- Success500 Internal Server Error- Database error
GET /api/rules/{id}
Get a single rule by ID.
Parameters:
id(path) - Rule ID (integer)
Response:
{
"id": 1,
"name": "Alert on new devices",
"description": "Notify when an unknown device joins the network",
"trigger_type": "new_device",
"mac_filter": null,
"enabled": true,
"notification_channels": ["telegram_123456789"],
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-15T10:00:00Z"
}Status Codes:
200 OK- Rule found404 Not Found- Rule does not exist500 Internal Server Error- Database error
POST /api/rules
Create a new notification rule.
Request Body:
{
"name": "New device alert",
"description": "Fires when a new device appears",
"trigger_type": "new_device",
"mac_filter": null,
"enabled": true,
"notification_channels": ["telegram_123456789", "ntfy_alerts"]
}Fields:
name(required) - Rule namedescription(optional) - Rule descriptiontrigger_type(required) - One of:new_device,device_connected,device_disconnected,device_status_changemac_filter(optional) - MAC address to filter (e.g.,aa:bb:cc:dd:ee:ff). Ifnull, rule applies to all devicesenabled(required) - Boolean, whether rule is activenotification_channels(required) - Array of channel names (use channel names from notification channels list)
Response:
Returns the created rule with assigned ID:
{
"id": 2,
"name": "New device alert",
"description": "Fires when a new device appears",
"trigger_type": "new_device",
"mac_filter": null,
"enabled": true,
"notification_channels": ["telegram_123456789", "ntfy_alerts"],
"created_at": "2025-01-15T14:00:00Z",
"updated_at": "2025-01-15T14:00:00Z"
}Status Codes:
200 OK- Rule created400 Bad Request- Invalid request data500 Internal Server Error- Database error
Example:
curl -X POST http://localhost:8080/api/rules \
-H "Content-Type: application/json" \
-d '{
"name": "Alert when laptop disconnects",
"description": "Notify if work laptop goes offline",
"trigger_type": "device_disconnected",
"mac_filter": "aa:bb:cc:dd:ee:ff",
"enabled": true,
"notification_channels": ["telegram_123456789"]
}'POST /api/rules/{id}
Update an existing rule.
Parameters:
id(path) - Rule ID (integer)
Request Body:
Same format as create rule (all fields required):
{
"name": "Updated rule name",
"description": "Updated description",
"trigger_type": "device_connected",
"mac_filter": null,
"enabled": false,
"notification_channels": ["ntfy_alerts"]
}Response:
Returns the updated rule:
{
"id": 1,
"name": "Updated rule name",
"description": "Updated description",
"trigger_type": "device_connected",
"mac_filter": null,
"enabled": false,
"notification_channels": ["ntfy_alerts"],
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-15T14:30:00Z"
}Status Codes:
200 OK- Rule updated404 Not Found- Rule does not exist400 Bad Request- Invalid request data500 Internal Server Error- Database error
POST /api/rules/{id}/delete
Delete a rule.
Parameters:
id(path) - Rule ID (integer)
Response:
{
"message": "Rule 1 deleted successfully"
}Status Codes:
200 OK- Rule deleted404 Not Found- Rule does not exist500 Internal Server Error- Database error
Example:
curl -X POST http://localhost:8080/api/rules/1/deleteConfiguration
GET /api/config
Get the current running configuration.
Response:
{
"daemon": {
"interface": "eth0",
"capture_filter": null,
"neighbor_check_interval_secs": 60,
"device_timeout_secs": 300,
"log_cleanup_enabled": true,
"log_retention_days": 30
},
"database": {
"path": "./foxd.db"
},
"api": {
"host": "0.0.0.0",
"port": 8080
}
}Status Codes:
200 OK- Success
POST /api/config
Update configuration at runtime.
Important: Changes to notification channels take effect immediately. Changes to daemon settings (interface, timeouts, etc.) require a daemon restart to take effect.
Request Body:
{
"daemon": {
"interface": "wlan0",
"capture_filter": "arp or (udp port 67 or udp port 68)",
"neighbor_check_interval_secs": 30,
"device_timeout_secs": 180,
"log_cleanup_enabled": true,
"log_retention_days": 7
}
}All daemon fields are required if the daemon object is provided.
Response:
{
"message": "Configuration updated successfully"
}Status Codes:
200 OK- Configuration updated400 Bad Request- Invalid configuration500 Internal Server Error- Failed to update
Example:
curl -X POST http://localhost:8080/api/config \
-H "Content-Type: application/json" \
-d '{
"daemon": {
"interface": "eth0",
"capture_filter": null,
"neighbor_check_interval_secs": 60,
"device_timeout_secs": 300,
"log_cleanup_enabled": true,
"log_retention_days": 30
}
}'Notification Channels
Notification channels define where alerts are sent. Channels are stored in the database and can be managed dynamically.
GET /api/notifications
List all configured notification channels.
Response:
{
"channels": [
{
"id": 1,
"channel": {
"type": "telegram",
"bot_token": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
"chat_id": "123456789"
}
},
{
"id": 2,
"channel": {
"type": "ntfy",
"server_url": "https://ntfy.sh",
"topic": "foxd-alerts",
"token": null
}
},
{
"id": 3,
"channel": {
"type": "webhook",
"url": "https://example.com/webhook",
"headers": {
"Authorization": "Bearer token123"
}
}
}
],
"count": 3
}Channel Types:
telegram- Send via Telegram botntfy- Send to ntfy.sh or self-hosted ntfy serverwebhook- POST to custom HTTP endpoint
Status Codes:
200 OK- Success500 Internal Server Error- Database error
GET /api/notifications/{id}
Get a specific notification channel by ID.
Parameters:
id(path) - Channel ID (integer)
Response:
{
"id": 1,
"channel": {
"type": "telegram",
"bot_token": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
"chat_id": "123456789"
}
}Status Codes:
200 OK- Channel found404 Not Found- Channel does not exist500 Internal Server Error- Database error
POST /api/notifications
Create a new notification channel.
Request Body - Telegram:
{
"type": "telegram",
"bot_token": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
"chat_id": "123456789"
}Request Body - ntfy:
{
"type": "ntfy",
"server_url": "https://ntfy.sh",
"topic": "foxd-alerts",
"token": "tk_xxxxxxxxxxxx"
}The token field is optional and can be null for public topics.
Request Body - Webhook:
{
"type": "webhook",
"url": "https://example.com/webhook",
"headers": {
"Authorization": "Bearer token123",
"X-Custom-Header": "value"
}
}The headers field is optional and can be null.
Response:
Returns the created channel with assigned ID:
{
"id": 4,
"channel": {
"type": "telegram",
"bot_token": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
"chat_id": "123456789"
}
}Status Codes:
200 OK- Channel created400 Bad Request- Invalid channel configuration500 Internal Server Error- Database error
Example:
curl -X POST http://localhost:8080/api/notifications \
-H "Content-Type: application/json" \
-d '{
"type": "telegram",
"bot_token": "YOUR_BOT_TOKEN",
"chat_id": "123456789"
}'PUT /api/notifications/{id}
Update an existing notification channel.
Parameters:
id(path) - Channel ID (integer)
Request Body:
Same format as create (provide complete channel configuration):
{
"type": "telegram",
"bot_token": "NEW_BOT_TOKEN",
"chat_id": "123456789"
}Response:
Returns the updated channel:
{
"id": 1,
"channel": {
"type": "telegram",
"bot_token": "NEW_BOT_TOKEN",
"chat_id": "123456789"
}
}Status Codes:
200 OK- Channel updated404 Not Found- Channel does not exist400 Bad Request- Invalid channel configuration500 Internal Server Error- Database error
Example:
curl -X PUT http://localhost:8080/api/notifications/1 \
-H "Content-Type: application/json" \
-d '{
"type": "telegram",
"bot_token": "NEW_BOT_TOKEN",
"chat_id": "123456789"
}'DELETE /api/notifications/{id}
Delete a notification channel.
Parameters:
id(path) - Channel ID (integer)
Response:
{
"message": "Notification channel 1 deleted successfully"
}Status Codes:
200 OK- Channel deleted404 Not Found- Channel does not exist500 Internal Server Error- Database error
Example:
curl -X DELETE http://localhost:8080/api/notifications/1Note: Deleting a channel does not automatically update rules that reference it. You should update or delete affected rules separately.
Metrics
GET /api/metrics
Get comprehensive daemon metrics and statistics.
Response:
{
"total_devices": 15,
"online_devices": 8,
"offline_devices": 7,
"total_rules": 3,
"enabled_rules": 2,
"packets_captured": 45230,
"notifications_sent": 12,
"uptime_seconds": 86400
}Fields:
total_devices- Total number of devices ever discoveredonline_devices- Currently online devicesoffline_devices- Currently offline devicestotal_rules- Total number of rules configuredenabled_rules- Number of enabled rulespackets_captured- Total packets processed since daemon startnotifications_sent- Total notifications sent since daemon startuptime_seconds- Daemon uptime in seconds
Status Codes:
200 OK- Success500 Internal Server Error- Database error
Example:
curl http://localhost:8080/api/metricsLogs
GET /api/logs
Get the most recent log entries (up to 200).
Response:
{
"logs": [
{
"id": 42,
"timestamp": "2025-01-15T12:00:00Z",
"level": "info",
"category": "device",
"message": "New device discovered: aa:bb:cc:dd:ee:ff",
"details": null
},
{
"id": 41,
"timestamp": "2025-01-15T11:58:30Z",
"level": "info",
"category": "notification",
"message": "Notification sent via telegram_123456789",
"details": "{\"device\": \"aa:bb:cc:dd:ee:ff\", \"event\": \"new_device\"}"
},
{
"id": 40,
"timestamp": "2025-01-15T11:55:00Z",
"level": "warning",
"category": "system",
"message": "High packet capture rate detected",
"details": null
}
],
"count": 3
}Log Levels:
info- Informational messageswarning- Warning messageserror- Error messagesdebug- Debug messages (if enabled)
Log Categories:
device- Device discovery and status changesnotification- Notification eventssystem- System-level eventsapi- API requests and responsesconfig- Configuration changes
Status Codes:
200 OK- Success500 Internal Server Error- Database error
Example:
curl http://localhost:8080/api/logsNote: Logs are automatically cleaned up based on the log_retention_days configuration setting. By default, logs older than 30 days are deleted.
Restart
POST /api/restart
Trigger a controlled daemon restart.
The daemon will exit with exit code 42, which can be detected by systemd or a wrapper script to perform a restart. This is useful for applying configuration changes that require a restart.
Response:
{
"message": "Daemon restart initiated"
}Status Codes:
200 OK- Restart initiated
Example:
curl -X POST http://localhost:8080/api/restartSystemd Integration:
If running under systemd with Restart=on-failure, the daemon will automatically restart after exit code 42. To configure this properly, use:
[Service]
Type=simple
ExecStart=/usr/local/bin/foxd
Restart=always
RestartSec=5Behavior:
- The API will respond immediately with a 200 status
- After 1 second, the daemon process will exit
- All active connections will be closed
- Database changes will be committed
- If running under systemd, the service will restart automatically
Channel Names
When referencing notification channels in rules, use the auto-generated channel name format:
| Channel Type | Name Format | Example |
|---|---|---|
| Telegram | telegram_{chat_id} | telegram_123456789 |
| ntfy | ntfy_{topic} | ntfy_alerts |
| Webhook | webhook_{last_url_segment} | webhook_endpoint |
These names are automatically generated and returned in the API responses from /api/notifications.
Data Types
Device Object
{
"id": 1,
"mac_address": "aa:bb:cc:dd:ee:ff",
"ip_address": "192.168.1.42",
"hostname": "device-name",
"nickname": "My Device",
"vendor": "Apple Inc.",
"status": "online",
"first_seen": "2025-01-15T10:00:00Z",
"last_seen": "2025-01-15T12:30:00Z"
}Rule Object
{
"id": 1,
"name": "Alert on new devices",
"description": "Notify when an unknown device joins",
"trigger_type": "new_device",
"mac_filter": null,
"enabled": true,
"notification_channels": ["telegram_123456789"],
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-15T10:00:00Z"
}Notification Channel Object
{
"id": 1,
"channel": {
"type": "telegram",
"bot_token": "...",
"chat_id": "123456789"
}
}Webhook Payload Format
When using webhook notification channels, foxd sends a POST request with the following JSON payload:
{
"timestamp": "2025-01-15T12:00:00Z",
"event_type": "new_device",
"device": {
"id": 1,
"mac_address": "aa:bb:cc:dd:ee:ff",
"ip_address": "192.168.1.42",
"hostname": "device-name",
"nickname": null,
"vendor": "Apple Inc.",
"status": "online",
"first_seen": "2025-01-15T12:00:00Z",
"last_seen": "2025-01-15T12:00:00Z"
},
"message": "New device discovered: device-name (aa:bb:cc:dd:ee:ff)"
}Event Types:
new_device- First time device is seendevice_connected- Device came onlinedevice_disconnected- Device went offlinedevice_status_change- Device status changed
Rate Limiting
The current version does not implement rate limiting. It is recommended to use network-level controls or a reverse proxy if rate limiting is needed.
CORS
CORS is configured permissively to allow the embedded web console to function. If exposing the API to external clients, consider implementing stricter CORS policies.
Best Practices
Polling: Avoid polling endpoints unnecessarily. Most data changes infrequently (devices, rules, config). The metrics endpoint can be polled more frequently if needed for monitoring.
Error Handling: Always check HTTP status codes and parse error responses from the API.
MAC Address Format: Always use lowercase MAC addresses with colon separators (
aa:bb:cc:dd:ee:ff).Channel Configuration: Test notification channels after creation to ensure they work correctly.
Configuration Changes: Remember that most daemon configuration changes require a restart. Use the
/api/restartendpoint after updating daemon settings.Database Backups: The SQLite database file should be backed up regularly. Stop the daemon before backing up, or use SQLite backup tools for online backups.