openapi: 3.1.0 info: title: Home Automation Backend (Python) description: Home automation backend with auth, runtime config, Home Assistant integrations, TickTick integration, and SQLite-backed recorders. version: 0.1.0 paths: /status: get: tags: - system summary: Get Status operationId: get_status_status_get responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/StatusResponse' /api/config: get: tags: - api-config summary: Get Config description: Return all configuration sections. Secret field values are masked (empty string). operationId: get_config_api_config_get responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ConfigResponse' put: tags: - api-config summary: Put Config description: 'Save configuration updates. - Blank secret value keeps the existing stored value (no change). - Invalid values return 422 and nothing is written to the database. - If MQTT-related settings changed, the MQTT client reconnects automatically.' operationId: put_config_api_config_put parameters: - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ConfigUpdateRequest' responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ConfigUpdateResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/config/smtp/test: post: tags: - api-config summary: Post Smtp Test description: 'Send a test SMTP email using the current runtime settings. Returns a structured result indicating success or the category of failure. Three possible outcomes: - 200 { "result": "success", "message": ... } - 400 { "result": "config-error", "message": ... } (EmailConfigurationError) - 502 { "result": "failed", "message": ... } (EmailDeliveryError) SMTP credentials are never echoed in the response.' operationId: post_smtp_test_api_config_smtp_test_post parameters: - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/SmtpTestResponse' '400': content: application/json: schema: $ref: '#/components/schemas/SmtpTestResponse' description: Bad Request '502': content: application/json: schema: $ref: '#/components/schemas/SmtpTestResponse' description: Bad Gateway '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/config/mqtt/test: post: tags: - api-config summary: Post Mqtt Test description: 'Test MQTT broker connectivity by attempting to connect and publishing a test message to ``/home-automation/test``. The message is visible in MQTT Explorer (or any subscriber) so users can confirm the full broker publish path is working. Three possible outcomes: - 200 { "result": "success", "message": ... } - 400 { "result": "config-error", "message": ... } (not configured) - 502 { "result": "failed", "message": ... } (connection/publish error) MQTT credentials are never echoed in the response.' operationId: post_mqtt_test_api_config_mqtt_test_post parameters: - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/MqttTestResponse' '400': content: application/json: schema: $ref: '#/components/schemas/MqttTestResponse' description: Bad Request '502': content: application/json: schema: $ref: '#/components/schemas/MqttTestResponse' description: Bad Gateway '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/locations: get: tags: - api-data summary: Get Locations description: 'Return location records with optional time-window filtering and pagination. - ``start`` / ``end`` are ISO8601 strings; filtering is **inclusive** on both bounds. - Results are ordered by ``datetime`` ascending. - ``limit`` is capped at 5000 to prevent full-table exports.' operationId: get_locations_api_locations_get parameters: - name: limit in: query required: false schema: type: integer maximum: 5000 minimum: 1 default: 1000 title: Limit - name: offset in: query required: false schema: type: integer minimum: 0 default: 0 title: Offset - name: start in: query required: false schema: anyOf: - type: string - type: 'null' title: Start - name: end in: query required: false schema: anyOf: - type: string - type: 'null' title: End responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/LocationsResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/poo: get: tags: - api-data summary: Get Poo description: 'Return poo records ordered by timestamp descending (most recent first). ``limit`` is capped at 1000 to prevent full-table exports.' operationId: get_poo_api_poo_get parameters: - name: limit in: query required: false schema: type: integer maximum: 1000 minimum: 1 default: 100 title: Limit - name: offset in: query required: false schema: type: integer minimum: 0 default: 0 title: Offset responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/PooResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/public-ip: get: tags: - api-data summary: Get Public Ip description: 'Return the current public IP state and recent history. - ``state`` is ``null`` if no IP check has been performed yet. - ``history`` is ordered by ``observed_at`` descending (most recent first). - ``limit`` applies to the history list and is capped at 1000.' operationId: get_public_ip_api_public_ip_get parameters: - name: limit in: query required: false schema: type: integer maximum: 1000 minimum: 1 default: 100 title: Limit responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/PublicIPResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/locations/{person}/{datetime}: patch: tags: - api-data summary: Patch Location description: 'Update the non-PK fields of a single location record. - ``person`` and ``datetime`` identify the row (composite PK) and are immutable. - Only ``latitude``, ``longitude``, and ``altitude`` may be updated. - Omitted body fields are left unchanged. - Returns **404** if the PK does not exist.' operationId: patch_location_api_locations__person___datetime__patch parameters: - name: person in: path required: true schema: type: string title: Person - name: datetime in: path required: true schema: type: string title: Datetime - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token requestBody: content: application/json: schema: $ref: '#/components/schemas/LocationUpdateRequest' default: {} responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/LocationRecord' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' delete: tags: - api-data summary: Delete Location Record description: 'Delete the single location record identified by its composite PK. - Exactly one row is deleted; **404** if the PK does not exist. - No batch delete / truncate path is available.' operationId: delete_location_record_api_locations__person___datetime__delete parameters: - name: person in: path required: true schema: type: string title: Person - name: datetime in: path required: true schema: type: string title: Datetime - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token responses: '204': description: Successful Response '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/poo/{timestamp}: patch: tags: - api-data summary: Patch Poo description: 'Update the non-PK fields of a single poo record. - ``timestamp`` is the PK and is immutable. - Only ``status``, ``latitude``, and ``longitude`` may be updated. - Omitted body fields are left unchanged. - Returns **404** if the PK does not exist.' operationId: patch_poo_api_poo__timestamp__patch parameters: - name: timestamp in: path required: true schema: type: string title: Timestamp - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token requestBody: content: application/json: schema: $ref: '#/components/schemas/PooUpdateRequest' default: {} responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/PooRecord' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' delete: tags: - api-data summary: Delete Poo description: 'Delete the single poo record identified by its PK. - Exactly one row is deleted; **404** if the PK does not exist. - No batch delete / truncate path is available.' operationId: delete_poo_api_poo__timestamp__delete parameters: - name: timestamp in: path required: true schema: type: string title: Timestamp - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token responses: '204': description: Successful Response '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/modbus/profiles: get: tags: - api-modbus summary: Get Profiles description: 'List all available Modbus YAML profiles (name + description). Intended for the front-end''s device-creation profile drop-down.' operationId: get_profiles_api_modbus_profiles_get responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ModbusProfilesResponse' /api/modbus/devices: get: tags: - api-modbus summary: List Devices description: Return all Modbus devices (no pagination — device counts stay small). operationId: list_devices_api_modbus_devices_get responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ModbusDeviceListResponse' post: tags: - api-modbus summary: Create Device description: 'Create a new Modbus device. - Validates that the referenced ``profile`` exists; returns 422 if not. - Returns 201 with the created device on success.' operationId: create_device_api_modbus_devices_post parameters: - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ModbusDeviceCreate' responses: '201': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ModbusDeviceResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/modbus/devices/{uuid}: get: tags: - api-modbus summary: Get Device description: Return a single Modbus device by UUID. operationId: get_device_api_modbus_devices__uuid__get parameters: - name: uuid in: path required: true schema: type: string title: Uuid responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ModbusDeviceResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' patch: tags: - api-modbus summary: Patch Device description: 'Partially update a Modbus device (including enable/disable). Only fields explicitly provided in the request body are updated. Providing ``profile`` triggers a profile-existence check (422 if unknown).' operationId: patch_device_api_modbus_devices__uuid__patch parameters: - name: uuid in: path required: true schema: type: string title: Uuid - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ModbusDeviceUpdate' responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ModbusDeviceResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' delete: tags: - api-modbus summary: Delete Device description: 'Delete a Modbus device. Returns 409 Conflict if the device has any associated readings; use ``enabled=false`` to disable it instead. **Application-layer safety**: the check is performed via an explicit SELECT COUNT query — not by relying on SQLite''s FK RESTRICT constraint, which is not enforced at runtime in this project (no PRAGMA foreign_keys=ON).' operationId: delete_device_api_modbus_devices__uuid__delete parameters: - name: uuid in: path required: true schema: type: string title: Uuid - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token responses: '204': description: Successful Response '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/modbus/devices/{uuid}/latest: get: tags: - api-modbus summary: Get Latest Reading description: 'Return the most recent reading for a device. If no readings exist yet, returns ``{"found": false, "recorded_at": null, "payload": null}`` (200, not 404) so the front-end can distinguish "device exists but has no data" from "device not found".' operationId: get_latest_reading_api_modbus_devices__uuid__latest_get parameters: - name: uuid in: path required: true schema: type: string title: Uuid responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ModbusLatestResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/modbus/devices/{uuid}/readings: get: tags: - api-modbus summary: Get Readings description: 'Return time-range readings for a device. When the window contains more rows than ``limit``, the **most recent** N rows are returned (``ORDER BY recorded_at DESC LIMIT n``), then reversed to ascending order before being sent to the client. This ensures that for long time-range requests (e.g. 6 h / 24 h) the caller always sees the latest data rather than the oldest segment of the window. When the window has fewer rows than ``limit`` the full window is returned in ascending order — behaviour is identical to a plain ascending query. The response schema is unchanged: items are always ``recorded_at`` ascending. The query uses the ``(device_id, recorded_at)`` composite index for efficient time-window scans. Query parameters: - ``start``: inclusive lower bound (ISO8601 datetime) - ``end``: inclusive upper bound (ISO8601 datetime) - ``limit``: max rows to return (default 500, max {_READINGS_LIMIT_MAX})' operationId: get_readings_api_modbus_devices__uuid__readings_get parameters: - name: uuid in: path required: true schema: type: string title: Uuid - name: start in: query required: false schema: anyOf: - type: string format: date-time - type: 'null' title: Start - name: end in: query required: false schema: anyOf: - type: string format: date-time - type: 'null' title: End - name: limit in: query required: false schema: type: integer maximum: 5000 minimum: 1 default: 500 title: Limit responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ModbusReadingsResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/modbus/devices/{uuid}/metrics: get: tags: - api-modbus summary: Get Metrics description: 'Return the metric catalogue for a device''s profile. Each entry has ``key``, ``label`` (derived from key if the profile has none — underscores → spaces, title-cased), ``unit``, and ``device_class``. This is the authoritative metadata source for front-end card labels and chart axis labels.' operationId: get_metrics_api_modbus_devices__uuid__metrics_get parameters: - name: uuid in: path required: true schema: type: string title: Uuid responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ModbusMetricsResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/modbus/devices/{uuid}/test: post: tags: - api-modbus summary: Test Read description: 'Immediately read and decode the device once without persisting any data. Useful for validating gateway connectivity and Modbus address settings before relying on the background polling job. This is a write-class endpoint (it initiates network I/O on demand) and therefore requires a CSRF token. The response payload is never stored.' operationId: test_read_api_modbus_devices__uuid__test_post parameters: - name: uuid in: path required: true schema: type: string title: Uuid - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ModbusTestReadResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/session: get: tags: - api-session summary: Get Session description: Return the current session user and CSRF token. Returns 401 if not authenticated. operationId: get_session_api_session_get responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/SessionResponse' /api/auth/login: post: tags: - api-session summary: Post Login description: 'Authenticate with username and password. On success, sets an HttpOnly session cookie and returns the session user + CSRF token. On failure, returns 401 with no cookie set. Repeated failures trigger exponential back-off (429 + Retry-After). No X-CSRF-Token required (unauthenticated endpoint).' operationId: post_login_api_auth_login_post requestBody: content: application/json: schema: $ref: '#/components/schemas/LoginRequest' required: true responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/SessionResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/auth/logout: post: tags: - api-session summary: Post Logout description: 'Revoke the current session and clear the session cookie. Requires authentication and X-CSRF-Token header. Returns 204 No Content.' operationId: post_logout_api_auth_logout_post parameters: - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/auth/password: post: tags: - api-session summary: Post Change Password description: 'Change the current user''s password. Requires authentication and X-CSRF-Token header. On AuthPasswordChangeError returns 400 with a generic message. On success, force_password_change becomes False (handled by the service). Returns 204 No Content.' operationId: post_change_password_api_auth_password_post parameters: - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PasswordChangeRequest' responses: '200': description: Successful Response content: application/json: schema: {} '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/auth/totp/setup: post: tags: - api-session summary: Post Totp Setup description: 'Generate a new pending TOTP secret, otpauth URI, and one-time recovery codes. The secret is stored in the DB but TOTP is NOT yet enabled (totp_enabled stays False until the user confirms with POST /api/auth/totp/enable). Recovery codes are returned here as plaintext exactly once; their Argon2 hashes are persisted immediately so enable only needs to flip the enabled flag. Repeating this call replaces any prior pending secret and regenerates codes. Requires: session cookie + X-CSRF-Token.' operationId: post_totp_setup_api_auth_totp_setup_post parameters: - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/TotpSetupResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/auth/totp/enable: post: tags: - api-session summary: Post Totp Enable description: 'Enable TOTP by confirming with the current 6-digit code from the authenticator app. Requires a prior call to POST /api/auth/totp/setup (so that a pending secret exists). On success, totp_enabled becomes True. Returns 400 if the code is wrong or there is no pending secret. Requires: session cookie + X-CSRF-Token.' operationId: post_totp_enable_api_auth_totp_enable_post parameters: - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TotpEnableRequest' responses: '204': description: Successful Response '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/auth/totp/disable: post: tags: - api-session summary: Post Totp Disable description: 'Disable TOTP. The caller must provide exactly one of: - ``password``: the user''s current login password, OR - ``code``: the current 6-digit TOTP code. On success: totp_enabled=False, totp_secret cleared, all recovery codes deleted. Returns 400 if neither credential matches or neither is provided. Requires: session cookie + X-CSRF-Token.' operationId: post_totp_disable_api_auth_totp_disable_post parameters: - name: X-CSRF-Token in: header required: false schema: anyOf: - type: string - type: 'null' title: X-Csrf-Token requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TotpDisableRequest' responses: '204': description: Successful Response '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/auth/totp: get: tags: - api-session summary: Get Totp Status description: 'Return the current TOTP status for the authenticated user. Response contains only ``{"enabled": bool}``. Secret and recovery codes are NEVER returned here. Requires: session cookie only (no CSRF — read-only).' operationId: get_totp_status_api_auth_totp_get responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/TotpStatusResponse' /homeassistant/publish: post: tags: - homeassistant summary: Publish From Homeassistant operationId: publish_from_homeassistant_homeassistant_publish_post responses: '200': description: Successful Response content: application/json: schema: {} /location/record: post: tags: - location summary: Create Location Record operationId: create_location_record_location_record_post responses: '200': description: Successful Response content: application/json: schema: {} /poo/record: post: tags: - poo summary: Create Poo Record operationId: create_poo_record_poo_record_post responses: '200': description: Successful Response content: application/json: schema: {} /poo/latest: get: tags: - poo summary: Notify Latest Poo operationId: notify_latest_poo_poo_latest_get responses: '200': description: Successful Response content: application/json: schema: {} /public-ip/check: get: tags: - public-ip summary: Run Public Ip Check operationId: run_public_ip_check_public_ip_check_get responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/PublicIPCheckResponse' /ticktick/auth/start: get: tags: - ticktick summary: Start Ticktick Auth operationId: start_ticktick_auth_ticktick_auth_start_get responses: '200': description: Successful Response content: application/json: schema: {} /ticktick/auth/code: get: tags: - ticktick summary: Handle Ticktick Auth Code operationId: handle_ticktick_auth_code_ticktick_auth_code_get responses: '200': description: Successful Response content: application/json: schema: {} components: schemas: ConfigField: properties: env_name: type: string title: Env Name label: type: string title: Label value: type: string title: Value secret: type: boolean title: Secret input_type: type: string title: Input Type configured: type: boolean title: Configured type: object required: - env_name - label - value - secret - input_type - configured title: ConfigField ConfigResponse: properties: sections: items: $ref: '#/components/schemas/ConfigSection' type: array title: Sections type: object required: - sections title: ConfigResponse ConfigSection: properties: name: type: string title: Name fields: items: $ref: '#/components/schemas/ConfigField' type: array title: Fields type: object required: - name - fields title: ConfigSection ConfigUpdateRequest: properties: updates: additionalProperties: type: string type: object title: Updates type: object required: - updates title: ConfigUpdateRequest description: Flat mapping of env_name → value, mirroring the existing form semantics. ConfigUpdateResponse: properties: sections: items: $ref: '#/components/schemas/ConfigSection' type: array title: Sections type: object required: - sections title: ConfigUpdateResponse HTTPValidationError: properties: detail: items: $ref: '#/components/schemas/ValidationError' type: array title: Detail type: object title: HTTPValidationError LocationRecord: properties: person: type: string title: Person datetime: type: string title: Datetime latitude: type: number title: Latitude longitude: type: number title: Longitude altitude: anyOf: - type: number - type: 'null' title: Altitude type: object required: - person - datetime - latitude - longitude - altitude title: LocationRecord LocationUpdateRequest: properties: latitude: anyOf: - type: number - type: 'null' title: Latitude longitude: anyOf: - type: number - type: 'null' title: Longitude altitude: anyOf: - type: number - type: 'null' title: Altitude type: object title: LocationUpdateRequest description: PATCH body for a location record — all fields optional; PK fields excluded. LocationsResponse: properties: items: items: $ref: '#/components/schemas/LocationRecord' type: array title: Items limit: type: integer title: Limit offset: type: integer title: Offset type: object required: - items - limit - offset title: LocationsResponse LoginRequest: properties: username: type: string title: Username password: type: string title: Password totp_code: anyOf: - type: string - type: 'null' title: Totp Code type: object required: - username - password title: LoginRequest MetricInfo: properties: key: type: string title: Key label: type: string title: Label unit: type: string title: Unit device_class: type: string title: Device Class type: object required: - key - label - unit - device_class title: MetricInfo description: Metadata for a single measurable quantity in a device's profile. ModbusDeviceCreate: properties: friendly_name: type: string maxLength: 255 minLength: 1 title: Friendly Name transport: type: string maxLength: 16 title: Transport default: tcp host: type: string maxLength: 255 minLength: 1 title: Host port: type: integer maximum: 65535.0 minimum: 1.0 title: Port default: 502 unit_id: type: integer maximum: 247.0 minimum: 0.0 title: Unit Id default: 1 profile: type: string maxLength: 64 minLength: 1 title: Profile poll_interval_s: type: integer maximum: 3600.0 minimum: 1.0 title: Poll Interval S default: 5 enabled: type: boolean title: Enabled default: true type: object required: - friendly_name - host - profile title: ModbusDeviceCreate description: Request body for POST /api/modbus/devices. ModbusDeviceListResponse: properties: items: items: $ref: '#/components/schemas/ModbusDeviceResponse' type: array title: Items total: type: integer title: Total type: object required: - items - total title: ModbusDeviceListResponse description: Response schema for listing Modbus devices. ModbusDeviceResponse: properties: uuid: type: string title: Uuid friendly_name: type: string title: Friendly Name transport: type: string title: Transport host: type: string title: Host port: type: integer title: Port unit_id: type: integer title: Unit Id profile: type: string title: Profile poll_interval_s: type: integer title: Poll Interval S enabled: type: boolean title: Enabled last_poll_at: anyOf: - type: string format: date-time - type: 'null' title: Last Poll At last_poll_ok: anyOf: - type: boolean - type: 'null' title: Last Poll Ok created_at: type: string format: date-time title: Created At updated_at: type: string format: date-time title: Updated At type: object required: - uuid - friendly_name - transport - host - port - unit_id - profile - poll_interval_s - enabled - last_poll_at - last_poll_ok - created_at - updated_at title: ModbusDeviceResponse description: Response schema for a single Modbus device. ModbusDeviceUpdate: properties: friendly_name: anyOf: - type: string maxLength: 255 minLength: 1 - type: 'null' title: Friendly Name transport: anyOf: - type: string maxLength: 16 - type: 'null' title: Transport host: anyOf: - type: string maxLength: 255 minLength: 1 - type: 'null' title: Host port: anyOf: - type: integer maximum: 65535.0 minimum: 1.0 - type: 'null' title: Port unit_id: anyOf: - type: integer maximum: 247.0 minimum: 0.0 - type: 'null' title: Unit Id profile: anyOf: - type: string maxLength: 64 minLength: 1 - type: 'null' title: Profile poll_interval_s: anyOf: - type: integer maximum: 3600.0 minimum: 1.0 - type: 'null' title: Poll Interval S enabled: anyOf: - type: boolean - type: 'null' title: Enabled type: object title: ModbusDeviceUpdate description: Request body for PATCH /api/modbus/devices/{uuid} — all fields optional. ModbusLatestResponse: properties: found: type: boolean title: Found recorded_at: anyOf: - type: string format: date-time - type: 'null' title: Recorded At payload: anyOf: - additionalProperties: true type: object - type: 'null' title: Payload type: object required: - found - recorded_at - payload title: ModbusLatestResponse description: 'Response for the /latest endpoint. ``found`` is False and ``recorded_at``/``payload`` are None when the device has no readings yet. Callers should check ``found`` before using the values.' ModbusMetricsResponse: properties: profile: type: string title: Profile metrics: items: $ref: '#/components/schemas/MetricInfo' type: array title: Metrics type: object required: - profile - metrics title: ModbusMetricsResponse description: Response schema for GET /api/modbus/devices/{uuid}/metrics. ModbusProfilesResponse: properties: profiles: items: $ref: '#/components/schemas/ProfileSummary' type: array title: Profiles type: object required: - profiles title: ModbusProfilesResponse description: Response schema for GET /api/modbus/profiles. ModbusReadingResponse: properties: recorded_at: type: string format: date-time title: Recorded At payload: additionalProperties: true type: object title: Payload type: object required: - recorded_at - payload title: ModbusReadingResponse description: 'A single reading row: timestamp + decoded payload.' ModbusReadingsResponse: properties: items: items: $ref: '#/components/schemas/ModbusReadingResponse' type: array title: Items type: object required: - items title: ModbusReadingsResponse description: Response schema for the readings time-range endpoint. ModbusTestReadResponse: properties: ok: type: boolean title: Ok payload: anyOf: - additionalProperties: true type: object - type: 'null' title: Payload error: anyOf: - type: string - type: 'null' title: Error type: object required: - ok title: ModbusTestReadResponse description: 'Response for POST /api/modbus/devices/{uuid}/test. On success ``ok=True`` and ``payload`` contains the decoded values. On failure ``ok=False`` and ``error`` describes the problem.' MqttTestResponse: properties: result: type: string enum: - success - config-error - failed title: Result message: type: string title: Message type: object required: - result - message title: MqttTestResponse description: Response from POST /api/config/mqtt/test. PasswordChangeRequest: properties: current_password: type: string title: Current Password new_password: type: string title: New Password confirm_password: type: string title: Confirm Password type: object required: - current_password - new_password - confirm_password title: PasswordChangeRequest PooRecord: properties: timestamp: type: string title: Timestamp status: type: string title: Status latitude: type: number title: Latitude longitude: type: number title: Longitude type: object required: - timestamp - status - latitude - longitude title: PooRecord PooResponse: properties: items: items: $ref: '#/components/schemas/PooRecord' type: array title: Items limit: type: integer title: Limit offset: type: integer title: Offset type: object required: - items - limit - offset title: PooResponse PooUpdateRequest: properties: status: anyOf: - type: string - type: 'null' title: Status latitude: anyOf: - type: number - type: 'null' title: Latitude longitude: anyOf: - type: number - type: 'null' title: Longitude type: object title: PooUpdateRequest description: PATCH body for a poo record — all fields optional; PK field excluded. ProfileSummary: properties: name: type: string title: Name description: type: string title: Description type: object required: - name - description title: ProfileSummary description: One entry in the GET /api/modbus/profiles response. PublicIPCheckResponse: properties: status: type: string enum: - first_seen - unchanged - changed - error title: Status checked_at: type: string format: date-time title: Checked At changed: type: boolean title: Changed type: object required: - status - checked_at - changed title: PublicIPCheckResponse PublicIPHistorySchema: properties: id: type: integer title: Id ipv4: type: string title: Ipv4 observed_at: type: string format: date-time title: Observed At change_type: type: string title: Change Type provider: anyOf: - type: string - type: 'null' title: Provider type: object required: - id - ipv4 - observed_at - change_type - provider title: PublicIPHistorySchema PublicIPResponse: properties: state: anyOf: - $ref: '#/components/schemas/PublicIPStateSchema' - type: 'null' history: items: $ref: '#/components/schemas/PublicIPHistorySchema' type: array title: History type: object required: - state - history title: PublicIPResponse PublicIPStateSchema: properties: id: type: integer title: Id current_ipv4: type: string title: Current Ipv4 previous_ipv4: anyOf: - type: string - type: 'null' title: Previous Ipv4 first_seen_at: type: string format: date-time title: First Seen At last_checked_at: type: string format: date-time title: Last Checked At last_changed_at: anyOf: - type: string format: date-time - type: 'null' title: Last Changed At last_check_status: type: string title: Last Check Status last_check_error: anyOf: - type: string - type: 'null' title: Last Check Error last_provider: anyOf: - type: string - type: 'null' title: Last Provider type: object required: - id - current_ipv4 - previous_ipv4 - first_seen_at - last_checked_at - last_changed_at - last_check_status - last_check_error - last_provider title: PublicIPStateSchema SessionResponse: properties: user: $ref: '#/components/schemas/SessionUser' csrf_token: type: string title: Csrf Token type: object required: - user - csrf_token title: SessionResponse SessionUser: properties: username: type: string title: Username force_password_change: type: boolean title: Force Password Change type: object required: - username - force_password_change title: SessionUser SmtpTestResponse: properties: result: type: string enum: - success - config-error - failed title: Result message: type: string title: Message type: object required: - result - message title: SmtpTestResponse description: Response from POST /api/config/smtp/test. StatusResponse: properties: status: type: string title: Status type: object required: - status title: StatusResponse TotpDisableRequest: properties: password: anyOf: - type: string - type: 'null' title: Password code: anyOf: - type: string - type: 'null' title: Code type: object title: TotpDisableRequest description: 'Disable TOTP by proving identity. Exactly one of ``password`` or ``code`` must be provided.' TotpEnableRequest: properties: code: type: string title: Code type: object required: - code title: TotpEnableRequest description: The user confirms setup by providing the 6-digit TOTP code. TotpSetupResponse: properties: secret: type: string title: Secret otpauth_uri: type: string title: Otpauth Uri recovery_codes: items: type: string type: array title: Recovery Codes type: object required: - secret - otpauth_uri - recovery_codes title: TotpSetupResponse description: 'Returned once after a setup call. ``secret`` and ``recovery_codes`` are **one-time plaintext values**. They are never returned again by any subsequent API call. The frontend must display and instruct the user to save them before confirming.' TotpStatusResponse: properties: enabled: type: boolean title: Enabled type: object required: - enabled title: TotpStatusResponse description: Minimal status response — never exposes secret or recovery codes. ValidationError: properties: loc: items: anyOf: - type: string - type: integer type: array title: Location msg: type: string title: Message type: type: string title: Error Type type: object required: - loc - msg - type title: ValidationError