Skip to content

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>/api

Default: 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 request
  • 201 Created - Resource created successfully (not currently used)
  • 204 No Content - Successful DELETE (not currently used)
  • 400 Bad Request - Invalid request data
  • 404 Not Found - Resource not found
  • 500 Internal Server Error - Server error

Error Response ​

All errors return a JSON object with an error field:

json
{
  "error": "Description of the error",
  "details": "Optional additional details"
}

Health ​

GET /api/health ​

Returns daemon status and system metrics.

Response:

json
{
  "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:

json
{
  "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 recently
  • offline - Device has not been seen within timeout period
  • unknown - Initial state before first status update

Status Codes:

  • 200 OK - Success
  • 500 Internal Server Error - Database error

GET /api/devices/{mac} ​

Get a single device by MAC address.

Parameters:

  • mac (path) - MAC address in format aa:bb:cc:dd:ee:ff (colons required)

Response:

json
{
  "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 found
  • 404 Not Found - Device does not exist
  • 500 Internal Server Error - Database error

Example:

bash
curl http://localhost:8080/api/devices/aa:bb:cc:dd:ee:ff

POST /api/devices/{mac}/nickname ​

Set or clear a device nickname.

Parameters:

  • mac (path) - MAC address in format aa:bb:cc:dd:ee:ff

Request Body:

json
{
  "nickname": "Dad's Laptop"
}

To clear a nickname, pass null:

json
{
  "nickname": null
}

Response:

Returns the updated device object:

json
{
  "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 updated
  • 404 Not Found - Device does not exist
  • 500 Internal Server Error - Database error

Example:

bash
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:

json
{
  "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 time
  • device_connected - Device comes online
  • device_disconnected - Device goes offline
  • device_status_change - Any status change (online ↔ offline)

Status Codes:

  • 200 OK - Success
  • 500 Internal Server Error - Database error

GET /api/rules/{id} ​

Get a single rule by ID.

Parameters:

  • id (path) - Rule ID (integer)

Response:

json
{
  "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 found
  • 404 Not Found - Rule does not exist
  • 500 Internal Server Error - Database error

POST /api/rules ​

Create a new notification rule.

Request Body:

json
{
  "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 name
  • description (optional) - Rule description
  • trigger_type (required) - One of: new_device, device_connected, device_disconnected, device_status_change
  • mac_filter (optional) - MAC address to filter (e.g., aa:bb:cc:dd:ee:ff). If null, rule applies to all devices
  • enabled (required) - Boolean, whether rule is active
  • notification_channels (required) - Array of channel names (use channel names from notification channels list)

Response:

Returns the created rule with assigned ID:

json
{
  "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 created
  • 400 Bad Request - Invalid request data
  • 500 Internal Server Error - Database error

Example:

bash
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):

json
{
  "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:

json
{
  "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 updated
  • 404 Not Found - Rule does not exist
  • 400 Bad Request - Invalid request data
  • 500 Internal Server Error - Database error

POST /api/rules/{id}/delete ​

Delete a rule.

Parameters:

  • id (path) - Rule ID (integer)

Response:

json
{
  "message": "Rule 1 deleted successfully"
}

Status Codes:

  • 200 OK - Rule deleted
  • 404 Not Found - Rule does not exist
  • 500 Internal Server Error - Database error

Example:

bash
curl -X POST http://localhost:8080/api/rules/1/delete

Configuration ​

GET /api/config ​

Get the current running configuration.

Response:

json
{
  "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:

json
{
  "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:

json
{
  "message": "Configuration updated successfully"
}

Status Codes:

  • 200 OK - Configuration updated
  • 400 Bad Request - Invalid configuration
  • 500 Internal Server Error - Failed to update

Example:

bash
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:

json
{
  "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 bot
  • ntfy - Send to ntfy.sh or self-hosted ntfy server
  • webhook - POST to custom HTTP endpoint

Status Codes:

  • 200 OK - Success
  • 500 Internal Server Error - Database error

GET /api/notifications/{id} ​

Get a specific notification channel by ID.

Parameters:

  • id (path) - Channel ID (integer)

Response:

json
{
  "id": 1,
  "channel": {
    "type": "telegram",
    "bot_token": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
    "chat_id": "123456789"
  }
}

Status Codes:

  • 200 OK - Channel found
  • 404 Not Found - Channel does not exist
  • 500 Internal Server Error - Database error

POST /api/notifications ​

Create a new notification channel.

Request Body - Telegram:

json
{
  "type": "telegram",
  "bot_token": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
  "chat_id": "123456789"
}

Request Body - ntfy:

json
{
  "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:

json
{
  "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:

json
{
  "id": 4,
  "channel": {
    "type": "telegram",
    "bot_token": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
    "chat_id": "123456789"
  }
}

Status Codes:

  • 200 OK - Channel created
  • 400 Bad Request - Invalid channel configuration
  • 500 Internal Server Error - Database error

Example:

bash
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):

json
{
  "type": "telegram",
  "bot_token": "NEW_BOT_TOKEN",
  "chat_id": "123456789"
}

Response:

Returns the updated channel:

json
{
  "id": 1,
  "channel": {
    "type": "telegram",
    "bot_token": "NEW_BOT_TOKEN",
    "chat_id": "123456789"
  }
}

Status Codes:

  • 200 OK - Channel updated
  • 404 Not Found - Channel does not exist
  • 400 Bad Request - Invalid channel configuration
  • 500 Internal Server Error - Database error

Example:

bash
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:

json
{
  "message": "Notification channel 1 deleted successfully"
}

Status Codes:

  • 200 OK - Channel deleted
  • 404 Not Found - Channel does not exist
  • 500 Internal Server Error - Database error

Example:

bash
curl -X DELETE http://localhost:8080/api/notifications/1

Note: 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:

json
{
  "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 discovered
  • online_devices - Currently online devices
  • offline_devices - Currently offline devices
  • total_rules - Total number of rules configured
  • enabled_rules - Number of enabled rules
  • packets_captured - Total packets processed since daemon start
  • notifications_sent - Total notifications sent since daemon start
  • uptime_seconds - Daemon uptime in seconds

Status Codes:

  • 200 OK - Success
  • 500 Internal Server Error - Database error

Example:

bash
curl http://localhost:8080/api/metrics

Logs ​

GET /api/logs ​

Get the most recent log entries (up to 200).

Response:

json
{
  "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 messages
  • warning - Warning messages
  • error - Error messages
  • debug - Debug messages (if enabled)

Log Categories:

  • device - Device discovery and status changes
  • notification - Notification events
  • system - System-level events
  • api - API requests and responses
  • config - Configuration changes

Status Codes:

  • 200 OK - Success
  • 500 Internal Server Error - Database error

Example:

bash
curl http://localhost:8080/api/logs

Note: 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:

json
{
  "message": "Daemon restart initiated"
}

Status Codes:

  • 200 OK - Restart initiated

Example:

bash
curl -X POST http://localhost:8080/api/restart

Systemd Integration:

If running under systemd with Restart=on-failure, the daemon will automatically restart after exit code 42. To configure this properly, use:

ini
[Service]
Type=simple
ExecStart=/usr/local/bin/foxd
Restart=always
RestartSec=5

Behavior:

  • 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 TypeName FormatExample
Telegramtelegram_{chat_id}telegram_123456789
ntfyntfy_{topic}ntfy_alerts
Webhookwebhook_{last_url_segment}webhook_endpoint

These names are automatically generated and returned in the API responses from /api/notifications.

Data Types ​

Device Object ​

json
{
  "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 ​

json
{
  "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 ​

json
{
  "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:

json
{
  "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 seen
  • device_connected - Device came online
  • device_disconnected - Device went offline
  • device_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 ​

  1. Polling: Avoid polling endpoints unnecessarily. Most data changes infrequently (devices, rules, config). The metrics endpoint can be polled more frequently if needed for monitoring.

  2. Error Handling: Always check HTTP status codes and parse error responses from the API.

  3. MAC Address Format: Always use lowercase MAC addresses with colon separators (aa:bb:cc:dd:ee:ff).

  4. Channel Configuration: Test notification channels after creation to ensure they work correctly.

  5. Configuration Changes: Remember that most daemon configuration changes require a restart. Use the /api/restart endpoint after updating daemon settings.

  6. 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.