{ "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" } } } } } } }, "/login": { "get": { "tags": [ "auth" ], "summary": "Login Page", "operationId": "login_page_login_get", "responses": { "200": { "description": "Successful Response", "content": { "text/html": { "schema": { "type": "string" } } } } } }, "post": { "tags": [ "auth" ], "summary": "Login Submit", "operationId": "login_submit_login_post", "requestBody": { "content": { "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/Body_login_submit_login_post" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "text/html": { "schema": { "type": "string" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/config/change-password": { "post": { "tags": [ "auth" ], "summary": "Change Password Submit", "operationId": "change_password_submit_config_change_password_post", "requestBody": { "content": { "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/Body_change_password_submit_config_change_password_post" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "text/html": { "schema": { "type": "string" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/logout": { "post": { "tags": [ "auth" ], "summary": "Logout", "operationId": "logout_logout_post", "requestBody": { "content": { "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/Body_logout_logout_post" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": {} } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/": { "get": { "tags": [ "pages" ], "summary": "Home", "operationId": "home__get", "responses": { "200": { "description": "Successful Response", "content": { "text/html": { "schema": { "type": "string" } } } } } } }, "/admin": { "get": { "tags": [ "pages" ], "summary": "Admin Redirect", "operationId": "admin_redirect_admin_get", "responses": { "200": { "description": "Successful Response", "content": { "text/html": { "schema": { "type": "string" } } } } } } }, "/config": { "get": { "tags": [ "pages" ], "summary": "Config Page", "operationId": "config_page_config_get", "responses": { "200": { "description": "Successful Response", "content": { "text/html": { "schema": { "type": "string" } } } } } }, "post": { "tags": [ "pages" ], "summary": "Config Submit", "operationId": "config_submit_config_post", "responses": { "200": { "description": "Successful Response", "content": { "text/html": { "schema": { "type": "string" } } } } } } }, "/config/smtp/test": { "post": { "tags": [ "pages" ], "summary": "Smtp Test Submit", "operationId": "smtp_test_submit_config_smtp_test_post", "responses": { "200": { "description": "Successful Response", "content": { "text/html": { "schema": { "type": "string" } } } } } } }, "/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.\n\n- Blank secret value keeps the existing stored value (no change).\n- 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/locations": { "get": { "tags": [ "api-data" ], "summary": "Get Locations", "description": "Return location records with optional time-window filtering and pagination.\n\n- ``start`` / ``end`` are ISO8601 strings; filtering is **inclusive** on both bounds.\n- Results are ordered by ``datetime`` ascending.\n- ``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).\n\n``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.\n\n- ``state`` is ``null`` if no IP check has been performed yet.\n- ``history`` is ordered by ``observed_at`` descending (most recent first).\n- ``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/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.\n\nOn success, sets an HttpOnly session cookie and returns the session user + CSRF token.\nOn failure, returns 401 with no cookie set.\nNo 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.\nRequires authentication and X-CSRF-Token header.\nReturns 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.\nRequires authentication and X-CSRF-Token header.\nOn AuthPasswordChangeError returns 400 with a generic message.\nOn success, force_password_change becomes False (handled by the service).\nReturns 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" } } } } } } }, "/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": { "Body_change_password_submit_config_change_password_post": { "properties": { "current_password": { "type": "string", "title": "Current Password" }, "new_password": { "type": "string", "title": "New Password" }, "confirm_password": { "type": "string", "title": "Confirm Password" }, "csrf_token": { "type": "string", "title": "Csrf Token" } }, "type": "object", "required": [ "current_password", "new_password", "confirm_password", "csrf_token" ], "title": "Body_change_password_submit_config_change_password_post" }, "Body_login_submit_login_post": { "properties": { "username": { "type": "string", "title": "Username" }, "password": { "type": "string", "title": "Password" }, "csrf_token": { "type": "string", "title": "Csrf Token" } }, "type": "object", "required": [ "username", "password", "csrf_token" ], "title": "Body_login_submit_login_post" }, "Body_logout_logout_post": { "properties": { "csrf_token": { "type": "string", "title": "Csrf Token" } }, "type": "object", "required": [ "csrf_token" ], "title": "Body_logout_logout_post" }, "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" }, "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" }, "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" }, "StatusResponse": { "properties": { "status": { "type": "string", "title": "Status" } }, "type": "object", "required": [ "status" ], "title": "StatusResponse" }, "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" } } } }