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.' 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/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/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 type: object required: - username - password title: LoginRequest 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. 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