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/expose: get: tags: - api-expose summary: Get Expose description: 'Return the full exposable-entity catalog with toggle states and MQTT status. The catalog is computed dynamically from registered providers (e.g. the Modbus provider enumerates all enabled devices and their metric entities). Toggle states come from the ``exposed_entity_toggle`` table; entities with no row default to ``enabled=False``.' operationId: get_expose_api_expose_get responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ExposeResponse' put: tags: - api-expose summary: Put Expose description: 'Set per-entity toggle state. Accepts a map of ``{key: bool}`` and upserts rows in the ``exposed_entity_toggle`` table. Only keys present in ``body.toggles`` are touched; other entities'' toggles are left unchanged. After writing the toggles, triggers a HA Discovery re-publish so any changes (enabled ↔ disabled) are reflected in Home Assistant immediately.' operationId: put_expose_api_expose_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/ExposeUpdateRequest' responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ExposeUpdateResponse' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' /api/expose/republish: post: tags: - api-expose summary: Post Expose Republish description: 'Manually trigger a full HA Discovery re-publish. Calls ``publish_discovery(session)`` from the HA discovery service (M5-T11). Returns a status indicating whether the publish was attempted (or skipped because MQTT / discovery is not enabled / connected).' operationId: post_expose_republish_api_expose_republish_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/RepublishResponse' '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: CatalogEntrySchema: properties: entity: $ref: '#/components/schemas/ExposableEntitySchema' enabled: type: boolean title: Enabled type: object required: - entity - enabled title: CatalogEntrySchema description: An entity from the catalog with its current toggle state. 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 DeviceInfoSchema: properties: identifiers: items: type: string type: array title: Identifiers name: type: string title: Name type: object required: - identifiers - name title: DeviceInfoSchema description: HA device grouping info for an exposable entity. ExposableEntitySchema: properties: key: type: string title: Key component: type: string title: Component device: $ref: '#/components/schemas/DeviceInfoSchema' device_class: anyOf: - type: string - type: 'null' title: Device Class unit: type: string title: Unit name: type: string title: Name state_class: anyOf: - type: string - type: 'null' title: State Class type: object required: - key - component - device - device_class - unit - name title: ExposableEntitySchema description: 'One exposable entity in the catalog. ``value_getter`` is intentionally excluded — it is a non-serialisable callable and is only used internally by the HA Discovery service.' ExposeResponse: properties: catalog: items: $ref: '#/components/schemas/CatalogEntrySchema' type: array title: Catalog mqtt_status: $ref: '#/components/schemas/MqttStatusSchema' type: object required: - catalog - mqtt_status title: ExposeResponse description: Response for GET /api/expose. ExposeUpdateRequest: properties: toggles: additionalProperties: type: boolean type: object title: Toggles type: object required: - toggles title: ExposeUpdateRequest description: 'Request body for PUT /api/expose. ``toggles`` is a map from entity key to desired enabled state (bool). Only keys present in the map are updated; absent keys are untouched.' ExposeUpdateResponse: properties: catalog: items: $ref: '#/components/schemas/CatalogEntrySchema' type: array title: Catalog mqtt_status: $ref: '#/components/schemas/MqttStatusSchema' type: object required: - catalog - mqtt_status title: ExposeUpdateResponse description: Response for PUT /api/expose (returns updated catalog + status). 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.' MqttStatusSchema: properties: mqtt_configured: type: boolean title: Mqtt Configured mqtt_connected: type: boolean title: Mqtt Connected discovery_enabled: type: boolean title: Discovery Enabled type: object required: - mqtt_configured - mqtt_connected - discovery_enabled title: MqttStatusSchema description: Connection status for MQTT and HA Discovery. 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 RepublishResponse: properties: ok: type: boolean title: Ok message: type: string title: Message type: object required: - ok - message title: RepublishResponse description: Response for POST /api/expose/republish. 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