Files
route-switcher/doc/API_DESIGN.md
Michal Humpula 940a86ff8c api
2026-02-16 07:23:36 +01:00

5.5 KiB

Route-Switcher API Design

Overview

HTTP REST API with Basic Authentication for Home Assistant integration, exposing state machine state and ping statistics.

Design Principles

  • Minimal surface area: Only expose necessary information
  • Simple authentication: HTTP Basic Auth (no JWT complexity)
  • State-focused: Centered on state machine state and ping history
  • Home Assistant friendly: Structured for HA REST integration
  • Opt-in: API disabled by default

API Endpoints

GET /api/state

Returns current state machine state with ping statistics.

Response:

{
  "state": "Primary",
  "primary_stats": {
    "success_rate": 95.5,
    "failures": 2,
    "total_pings": 44,
    "last_ping": "Ok"
  },
  "secondary_stats": {
    "success_rate": 98.2,
    "failures": 1,
    "total_pings": 56,
    "last_ping": "Ok"
  },
  "last_failover": "2024-02-15T10:30:00Z"
}

Fields:

  • state: Current state machine state (Boot/Primary/Fallback)
  • primary_stats: Ping statistics for primary interface
  • secondary_stats: Ping statistics for secondary interface
  • last_failover: ISO 8601 timestamp of last failover (null if never)

POST /api/state

Manually set state machine state.

Request:

{
  "state": "fallback"
}

Response:

{
  "state": "Fallback",
  "previous_state": "Primary",
  "primary_stats": { ... },
  "secondary_stats": { ... },
  "last_failover": "2024-02-15T10:30:00Z"
}

Valid states: primary, fallback

Authentication

HTTP Basic Authentication with username/password configured via environment variables.

Security considerations:

  • Passwords stored as bcrypt hash
  • HTTPS recommended for production
  • Local network access only
  • No token management (stateless)

Data Structures

PingStats

Calculated from state machine ping history (60 entries per interface):

struct PingStats {
    success_rate: f64,    // Percentage of successful pings
    failures: usize,      // Number of failed pings in history
    total_pings: usize,   // Total pings in history
    last_ping: String,     // "Ok" or "Failed"
}

StateResponse

struct StateResponse {
    state: String,
    primary_stats: PingStats,
    secondary_stats: PingStats,
    last_failover: Option<String>,
}

Home Assistant Integration

REST Sensor Configuration

sensor:
  - platform: rest
    name: Route Switcher State
    resource: http://route-switcher.local:8080/api/state
    username: !secret route_switcher_user
    password: !secret route_switcher_pass
    value_template: "{{ value_json.state }}"
    json_attributes:
      - primary_stats
      - secondary_stats
      - last_failover

  - platform: template
    sensors:
      route_switcher_primary_success_rate:
        value_template: "{{ state_attr('sensor.route_switcher_state', 'primary_stats').success_rate | default(0) }}"
        unit_of_measurement: "%"
      route_switcher_secondary_success_rate:
        value_template: "{{ state_attr('sensor.route_switcher_state', 'secondary_stats').success_rate | default(0) }}"
        unit_of_measurement: "%"
      route_switcher_primary_failures:
        value_template: "{{ state_attr('sensor.route_switcher_state', 'primary_stats').failures | default(0) }}"
      route_switcher_secondary_failures:
        value_template: "{{ state_attr('sensor.route_switcher_state', 'secondary_stats').failures | default(0) }}"

switch:
  - platform: rest
    name: Route Switcher Control
    resource: http://route-switcher.local:8080/api/state
    username: !secret route_switcher_user
    password: !secret route_switcher_pass
    body_on: '{"state": "fallback"}'
    body_off: '{"state": "primary"}'
    is_on_template: "{{ value_json.state == 'fallback' }}"

Configuration

Environment Variables

# API Configuration
API_ENABLED=true
API_BIND_ADDRESS=0.0.0.0
API_PORT=8080
API_USERNAME=admin
API_PASSWORD_HASH=<bcrypt-hash>

# CORS Configuration
API_CORS_ORIGINS=http://homeassistant.local:8123

Password Hash Generation

# Generate bcrypt hash
echo -n "your-password" | bcrypt

Implementation Details

Dependencies

axum = "0.7"
tokio = { version = "1.42", features = ["full"] }
tower = "0.4"
tower-http = { version = "0.5", features = ["cors", "auth"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
bcrypt = "0.15"
base64 = "0.22"

Architecture

  • API Module: src/api.rs - HTTP server and endpoints
  • State Sharing: Thread-safe access to state machine and ping history
  • Authentication: Basic Auth middleware with bcrypt validation
  • Error Handling: Standardized JSON error responses
  • Integration: Minimal changes to existing state machine

Thread Safety

  • Arc<Mutex<StateMachine>> for shared state access
  • Non-blocking async operations
  • Minimal locking duration

Error Handling

Standardized error responses:

{
  "error": "Invalid state",
  "message": "State must be 'primary' or 'fallback'"
}

HTTP Status Codes:

  • 200: Success
  • 400: Bad Request (invalid state)
  • 401: Unauthorized (invalid credentials)
  • 500: Internal Server Error

Security Considerations

  • Network access restrictions (local only recommended)
  • HTTPS for credential protection
  • Rate limiting considerations
  • Audit logging for manual state changes
  • No configuration exposure (state only)

Backward Compatibility

  • API disabled by default
  • No changes to existing CLI functionality
  • Service continues without API if disabled
  • Graceful degradation on API errors