5009 lines
146 KiB
JSON
5009 lines
146 KiB
JSON
{
|
||
"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.\n\n- Blank secret value keeps the existing stored value (no change).\n- Invalid values return 422 and nothing is written to the database.\n- 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.\n\nReturns a structured result indicating success or the category of failure.\nThree possible outcomes:\n- 200 { \"result\": \"success\", \"message\": ... }\n- 400 { \"result\": \"config-error\", \"message\": ... } (EmailConfigurationError)\n- 502 { \"result\": \"failed\", \"message\": ... } (EmailDeliveryError)\n\nSMTP 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\ntest message to ``<ha_discovery_prefix>/home-automation/test``.\n\nThe message is visible in MQTT Explorer (or any subscriber) so users can\nconfirm the full broker publish path is working.\n\nThree possible outcomes:\n- 200 { \"result\": \"success\", \"message\": ... }\n- 400 { \"result\": \"config-error\", \"message\": ... } (not configured)\n- 502 { \"result\": \"failed\", \"message\": ... } (connection/publish error)\n\nMQTT 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.\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/locations/{person}/{datetime}": {
|
||
"patch": {
|
||
"tags": [
|
||
"api-data"
|
||
],
|
||
"summary": "Patch Location",
|
||
"description": "Update the non-PK fields of a single location record.\n\n- ``person`` and ``datetime`` identify the row (composite PK) and are immutable.\n- Only ``latitude``, ``longitude``, and ``altitude`` may be updated.\n- Omitted body fields are left unchanged.\n- 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.\n\n- Exactly one row is deleted; **404** if the PK does not exist.\n- 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.\n\n- ``timestamp`` is the PK and is immutable.\n- Only ``status``, ``latitude``, and ``longitude`` may be updated.\n- Omitted body fields are left unchanged.\n- 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.\n\n- Exactly one row is deleted; **404** if the PK does not exist.\n- 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/energy/prices": {
|
||
"get": {
|
||
"tags": [
|
||
"api-energy"
|
||
],
|
||
"summary": "Get Prices",
|
||
"description": "Return the price curve for the active contract.\n\n**Tibber contracts** (kind=\"tibber\"):\n Fetches ``tibber_price`` rows within ``[start, end]``, ordered ascending\n by ``starts_at``. At most ``limit`` rows are returned (most recent first\n within the window, then reversed to ascending order — identical to the\n modbus readings pattern).\n\n Response ``points`` carries per-slot:\n - ``buy = total`` (Tibber all-inclusive price)\n - ``sell = total − energy_tax − sell_adjust`` (from active version values)\n - ``level`` (Tibber price level, may be null)\n\n ``tariff`` is null.\n\n**Manual contracts** (kind=\"manual\"):\n ``points`` is empty. ``tariff`` carries the four effective prices\n derived using the billing engine formula:\n - ``buy_dal = energy.buy.dal + energy_tax + ode``\n - ``buy_normal = energy.buy.normal + energy_tax + ode``\n - ``sell_dal = energy.sell.dal``\n - ``sell_normal = energy.sell.normal``\n\n**No active contract**: returns kind=null, currency=\"EUR\", points=[], tariff=null (200).",
|
||
"operationId": "get_prices_api_energy_prices_get",
|
||
"parameters": [
|
||
{
|
||
"name": "start",
|
||
"in": "query",
|
||
"required": false,
|
||
"schema": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Inclusive start of the time window (ISO 8601). Defaults to the start of today UTC when omitted.",
|
||
"title": "Start"
|
||
},
|
||
"description": "Inclusive start of the time window (ISO 8601). Defaults to the start of today UTC when omitted."
|
||
},
|
||
{
|
||
"name": "end",
|
||
"in": "query",
|
||
"required": false,
|
||
"schema": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Inclusive end of the time window (ISO 8601). Defaults to the end of tomorrow UTC when omitted.",
|
||
"title": "End"
|
||
},
|
||
"description": "Inclusive end of the time window (ISO 8601). Defaults to the end of tomorrow UTC when omitted."
|
||
},
|
||
{
|
||
"name": "limit",
|
||
"in": "query",
|
||
"required": false,
|
||
"schema": {
|
||
"type": "integer",
|
||
"maximum": 5000,
|
||
"minimum": 1,
|
||
"description": "Maximum number of Tibber price points to return.",
|
||
"default": 500,
|
||
"title": "Limit"
|
||
},
|
||
"description": "Maximum number of Tibber price points to return."
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Successful Response",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/PricesResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"422": {
|
||
"description": "Validation Error",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/HTTPValidationError"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/energy/costs": {
|
||
"get": {
|
||
"tags": [
|
||
"api-energy"
|
||
],
|
||
"summary": "Get Costs",
|
||
"description": "Return energy_cost_period rows within a time window.\n\nRows are ordered by ``period_start`` ascending. When the window contains\nmore rows than ``limit``, the **most recent** N rows are returned (DESC LIMIT),\nthen reversed to ascending order — identical to the modbus readings pattern.\n\nQuery parameters:\n- ``start``: inclusive lower bound on ``period_start`` (ISO 8601 datetime).\n- ``end``: inclusive upper bound on ``period_start`` (ISO 8601 datetime).\n- ``limit``: max rows to return (default 500, max {_COSTS_LIMIT_MAX}).",
|
||
"operationId": "get_costs_api_energy_costs_get",
|
||
"parameters": [
|
||
{
|
||
"name": "start",
|
||
"in": "query",
|
||
"required": false,
|
||
"schema": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Inclusive lower bound for period_start (ISO 8601).",
|
||
"title": "Start"
|
||
},
|
||
"description": "Inclusive lower bound for period_start (ISO 8601)."
|
||
},
|
||
{
|
||
"name": "end",
|
||
"in": "query",
|
||
"required": false,
|
||
"schema": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Inclusive upper bound for period_start (ISO 8601).",
|
||
"title": "End"
|
||
},
|
||
"description": "Inclusive upper bound for period_start (ISO 8601)."
|
||
},
|
||
{
|
||
"name": "limit",
|
||
"in": "query",
|
||
"required": false,
|
||
"schema": {
|
||
"type": "integer",
|
||
"maximum": 5000,
|
||
"minimum": 1,
|
||
"description": "Maximum number of cost periods to return (default 500, max 5000).",
|
||
"default": 500,
|
||
"title": "Limit"
|
||
},
|
||
"description": "Maximum number of cost periods to return (default 500, max 5000)."
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Successful Response",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/CostsResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"422": {
|
||
"description": "Validation Error",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/HTTPValidationError"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/energy/costs/summary": {
|
||
"get": {
|
||
"tags": [
|
||
"api-energy"
|
||
],
|
||
"summary": "Get Costs Summary",
|
||
"description": "Aggregate billing for a time interval.\n\nCalls ``energy_cost.summarize(session, start, end)`` which computes:\n\n total_payable = Σ(net_cost) + fixed_costs − credits\n\nwhere ``fixed_costs`` is (network_fee + management_fee) apportioned to the\ninterval length in days (÷30 per month), and ``credits`` is heffingskorting\napportioned similarly (÷365 per year).\n\nBoth ``fixed_costs`` and ``credits`` are derived from the **currently active\ncontract version at ``end``**. When no active contract exists they are 0.",
|
||
"operationId": "get_costs_summary_api_energy_costs_summary_get",
|
||
"parameters": [
|
||
{
|
||
"name": "start",
|
||
"in": "query",
|
||
"required": false,
|
||
"schema": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Inclusive start of the summary interval (ISO 8601). Defaults to the start of the current UTC day.",
|
||
"title": "Start"
|
||
},
|
||
"description": "Inclusive start of the summary interval (ISO 8601). Defaults to the start of the current UTC day."
|
||
},
|
||
{
|
||
"name": "end",
|
||
"in": "query",
|
||
"required": false,
|
||
"schema": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Exclusive end of the summary interval (ISO 8601). Defaults to the start of the next UTC day (i.e. today's full data).",
|
||
"title": "End"
|
||
},
|
||
"description": "Exclusive end of the summary interval (ISO 8601). Defaults to the start of the next UTC day (i.e. today's full data)."
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Successful Response",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/SummaryResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"422": {
|
||
"description": "Validation Error",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/HTTPValidationError"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/energy/dsmr/latest": {
|
||
"get": {
|
||
"tags": [
|
||
"api-energy"
|
||
],
|
||
"summary": "Get Dsmr Latest",
|
||
"description": "Return the most recent dsmr_reading row.\n\nReturns ``{\"found\": false, \"recorded_at\": null, \"payload\": null}`` (200, not\n404) when no rows exist yet, so the front-end can distinguish \"no data\" from\na server error.",
|
||
"operationId": "get_dsmr_latest_api_energy_dsmr_latest_get",
|
||
"responses": {
|
||
"200": {
|
||
"description": "Successful Response",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/DsmrLatestResponse"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/energy/costs/recompute": {
|
||
"post": {
|
||
"tags": [
|
||
"api-energy"
|
||
],
|
||
"summary": "Post Recompute",
|
||
"description": "Idempotently recompute billing records in a time window.\n\nCalls ``energy_cost.recompute_range(session, start, end)`` which overwrites\nexisting rows (including successful ones) for every UTC quarter-hour boundary\nin ``[start, end)``.\n\n**Idempotency**: repeated calls with the same window produce the same\noutcome. No rows are deleted; only upserted.\n\n**Window constraint**: the maximum allowed range is {_RECOMPUTE_MAX_DAYS} days.\nRequests exceeding this return 422.\n\nReturns the number of periods for which a billing record was written.\nPeriods skipped due to missing contract or missing Tibber price are not counted.",
|
||
"operationId": "post_recompute_api_energy_costs_recompute_post",
|
||
"parameters": [
|
||
{
|
||
"name": "start",
|
||
"in": "query",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"description": "Inclusive start of the recompute window (ISO 8601). Required.",
|
||
"title": "Start"
|
||
},
|
||
"description": "Inclusive start of the recompute window (ISO 8601). Required."
|
||
},
|
||
{
|
||
"name": "end",
|
||
"in": "query",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"description": "Exclusive end of the recompute window (ISO 8601). Required.",
|
||
"title": "End"
|
||
},
|
||
"description": "Exclusive end of the recompute window (ISO 8601). Required."
|
||
},
|
||
{
|
||
"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/RecomputeResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"422": {
|
||
"description": "Validation error (missing window or range too large)"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/energy/tibber/test": {
|
||
"post": {
|
||
"tags": [
|
||
"api-energy"
|
||
],
|
||
"summary": "Post Tibber Test",
|
||
"description": "Test Tibber API connectivity by fetching the current price point.\n\nThree possible outcomes:\n\n- **200** ``{ result: \"success\", message: ..., price: {...} }``\n The Tibber API responded with a valid current price. ``price`` contains\n starts_at, total, energy, tax, currency, and level.\n\n- **400** ``{ result: \"config-error\", message: ... }``\n The Tibber API token is empty or not configured.\n\n- **502** ``{ result: \"failed\", message: ... }``\n The API call failed (authentication rejected, network error, timeout,\n unexpected response, etc.).\n\nThe API token is **never** included in the response body or logged.",
|
||
"operationId": "post_tibber_test_api_energy_tibber_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/TibberTestResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"400": {
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/TibberTestResponse"
|
||
}
|
||
}
|
||
},
|
||
"description": "Bad Request"
|
||
},
|
||
"502": {
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/TibberTestResponse"
|
||
}
|
||
}
|
||
},
|
||
"description": "Bad Gateway"
|
||
},
|
||
"422": {
|
||
"description": "Validation Error",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/HTTPValidationError"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/energy/profiles": {
|
||
"get": {
|
||
"tags": [
|
||
"api-energy-contracts"
|
||
],
|
||
"summary": "Get Profiles",
|
||
"description": "List all available pricing profile structures.\n\nReturns the full profile structure for each supported contract kind\n(``manual`` and ``tibber``). The front-end uses this to dynamically\nrender the correct fields and labels for the contract creation/editing form.",
|
||
"operationId": "get_profiles_api_energy_profiles_get",
|
||
"responses": {
|
||
"200": {
|
||
"description": "Successful Response",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/ProfilesResponse"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/energy/contracts": {
|
||
"get": {
|
||
"tags": [
|
||
"api-energy-contracts"
|
||
],
|
||
"summary": "List Energy Contracts",
|
||
"description": "List all energy contracts with their active status.\n\nReturns a flat list (no embedded version history); use\nGET /api/energy/contracts/{id} to fetch the full version history for a\nspecific contract.",
|
||
"operationId": "list_energy_contracts_api_energy_contracts_get",
|
||
"responses": {
|
||
"200": {
|
||
"description": "Successful Response",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/ContractListResponse"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"post": {
|
||
"tags": [
|
||
"api-energy-contracts"
|
||
],
|
||
"summary": "Create Energy Contract",
|
||
"description": "Create a new energy contract with an initial pricing version.\n\nThe ``values`` dict is validated against the YAML profile for the given\n``kind``; non-conforming values result in 422 Unprocessable Entity.\nThe new contract is created with ``active=False``; use\nPATCH /api/energy/contracts/{id} with ``active=true`` to activate it.",
|
||
"operationId": "create_energy_contract_api_energy_contracts_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/ContractCreate"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"201": {
|
||
"description": "Successful Response",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/ContractDetailResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"422": {
|
||
"description": "Validation Error",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/HTTPValidationError"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/energy/contracts/{contract_id}": {
|
||
"get": {
|
||
"tags": [
|
||
"api-energy-contracts"
|
||
],
|
||
"summary": "Get Energy Contract",
|
||
"description": "Return a single energy contract with its full version history.\n\nVersions are ordered by ``effective_from`` ascending so the caller can\neasily inspect the pricing timeline.",
|
||
"operationId": "get_energy_contract_api_energy_contracts__contract_id__get",
|
||
"parameters": [
|
||
{
|
||
"name": "contract_id",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "integer",
|
||
"title": "Contract Id"
|
||
}
|
||
}
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "Successful Response",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/ContractDetailResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"422": {
|
||
"description": "Validation Error",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/HTTPValidationError"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"patch": {
|
||
"tags": [
|
||
"api-energy-contracts"
|
||
],
|
||
"summary": "Patch Energy Contract",
|
||
"description": "Partially update a contract: rename or change activation status.\n\n- ``name``: updates the human-readable label.\n- ``active=true``: activates this contract (all others are deactivated).\n- ``active=false``: deactivates this contract (no effect on others).\n\nAt most one contract may be active at any time; the service layer enforces\nmutual exclusion.",
|
||
"operationId": "patch_energy_contract_api_energy_contracts__contract_id__patch",
|
||
"parameters": [
|
||
{
|
||
"name": "contract_id",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "integer",
|
||
"title": "Contract Id"
|
||
}
|
||
},
|
||
{
|
||
"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/ContractPatch"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "Successful Response",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/ContractDetailResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"422": {
|
||
"description": "Validation Error",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/HTTPValidationError"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/energy/contracts/{contract_id}/versions": {
|
||
"post": {
|
||
"tags": [
|
||
"api-energy-contracts"
|
||
],
|
||
"summary": "Add Contract Version",
|
||
"description": "Add a new pricing version to an existing contract.\n\nThis is how price changes are recorded: the current open version is\nautomatically closed (its ``effective_to`` is set to ``body.effective_from``)\nand a new version is created starting at ``body.effective_from``.\n\nThe ``values`` dict must conform to the contract's pricing profile.\nNon-conforming values return 422. If ``effective_from`` is not strictly\nafter the previous version's ``effective_from``, 422 is returned without\nwriting any rows.\n\nHistorical versions are never modified; this endpoint is append-only.",
|
||
"operationId": "add_contract_version_api_energy_contracts__contract_id__versions_post",
|
||
"parameters": [
|
||
{
|
||
"name": "contract_id",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "integer",
|
||
"title": "Contract Id"
|
||
}
|
||
},
|
||
{
|
||
"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/VersionCreate"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"201": {
|
||
"description": "Successful Response",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/ContractDetailResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"422": {
|
||
"description": "Validation Error",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/HTTPValidationError"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/energy/meters": {
|
||
"get": {
|
||
"tags": [
|
||
"api-energy-meters"
|
||
],
|
||
"summary": "List Energy Meters",
|
||
"description": "List all meter epochs in ascending ``started_at`` order.\n\nReturns the full historical sequence of meter installations across all\ncommodities. The active meter (``ended_at=null``) appears last because it\nhas the latest ``started_at``.",
|
||
"operationId": "list_energy_meters_api_energy_meters_get",
|
||
"responses": {
|
||
"200": {
|
||
"description": "Successful Response",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/MeterListResponse"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"post": {
|
||
"tags": [
|
||
"api-energy-meters"
|
||
],
|
||
"summary": "Declare Energy Meter",
|
||
"description": "Declare a new meter epoch (swap, home move, or initial declaration).\n\nCloses the current active meter for the given commodity at ``started_at``\nand opens a new active meter. If no active meter exists, the new meter is\nsimply created without closing anything.\n\n**Validation**: ``started_at`` must be **≥** the current active meter's\nown ``started_at`` (no chronological backdate below the active epoch's\nstart). Equal timestamps are allowed (replaces the current meter at the\nsame logical moment). Violation → 422.\n\n**Retroactive recompute**: if ``started_at`` is in the past, billing\nrecords from that point forward are re-judged via ``recompute_range`` to\nreflect the new meter attribution. The recompute is transparent — the\nresponse body is the created meter (``MeterResponse``) only and does **not**\ninclude a recompute count; callers should re-fetch costs if they need the\nupdated totals.",
|
||
"operationId": "declare_energy_meter_api_energy_meters_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/MeterDeclareRequest"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"201": {
|
||
"description": "Successful Response",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/MeterResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"422": {
|
||
"description": "Validation Error",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/HTTPValidationError"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/api/energy/meters/{meter_id}": {
|
||
"patch": {
|
||
"tags": [
|
||
"api-energy-meters"
|
||
],
|
||
"summary": "Patch Energy Meter",
|
||
"description": "Partially update a meter epoch: rename, edit note, or correct started_at.\n\n- ``label``: updates the human-readable label.\n- ``note``: updates the free-form note.\n- ``started_at``: **retroactive correction** — shifts this meter's start\n boundary. The service layer maintains timeline continuity by also\n updating the preceding meter's ``ended_at``. Validation:\n * Must be strictly after the previous meter's own ``started_at``.\n * Must be strictly before this meter's ``ended_at`` (if closed).\n Violation → 422.\n\n**Retroactive recompute when ``started_at`` changes**: billing records in\nthe window ``[min(old, new), now)`` are re-judged to reflect the corrected\nmeter attribution.\n\nNot found → 404.",
|
||
"operationId": "patch_energy_meter_api_energy_meters__meter_id__patch",
|
||
"parameters": [
|
||
{
|
||
"name": "meter_id",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "integer",
|
||
"title": "Meter Id"
|
||
}
|
||
},
|
||
{
|
||
"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/MeterPatchRequest"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "Successful Response",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": {
|
||
"$ref": "#/components/schemas/MeterResponse"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"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.\n\nThe catalog is computed dynamically from registered providers (e.g. the\nModbus provider enumerates all enabled devices and their metric entities).\nToggle states come from the ``exposed_entity_toggle`` table; entities with\nno 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.\n\nAccepts a map of ``{key: bool}`` and upserts rows in the\n``exposed_entity_toggle`` table. Only keys present in ``body.toggles``\nare touched; other entities' toggles are left unchanged.\n\nAfter writing the toggles, triggers a HA Discovery re-publish so any\nchanges (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.\n\nCalls ``publish_discovery(session)`` from the HA discovery service (M5-T11).\nReturns a status indicating whether the publish was attempted (or skipped\nbecause 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).\n\nIntended 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.\n\n- Validates that the referenced ``profile`` exists; returns 422 if not.\n- 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).\n\nOnly fields explicitly provided in the request body are updated.\nProviding ``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.\n\n**Default behaviour (cascade=false)**:\nReturns 409 Conflict if the device has any associated readings; use\n``enabled=false`` to disable it instead.\n\n**Cascade delete (cascade=true)**:\nPermanently deletes the device together with all its readings and any\n``ExposedEntityToggle`` rows whose key matches ``modbus.<uuid>.*``.\nAlso makes a best-effort attempt to clear the device's HA Discovery\nconfig topics from MQTT (empty retained payload) before the DB rows\nare removed. MQTT failures are swallowed — the DB deletion proceeds\nregardless.\nReturns HTTP 200 with a ``ModbusDeleteResponse`` JSON body on success.\n\n**Application-layer safety**: the 409 guard uses an explicit SELECT COUNT\nquery to return a friendly message. FK RESTRICT is enforced at runtime\n(the app sets ``PRAGMA foreign_keys=ON``), so the cascade path deletes\nreadings before the device.",
|
||
"operationId": "delete_device_api_modbus_devices__uuid__delete",
|
||
"parameters": [
|
||
{
|
||
"name": "uuid",
|
||
"in": "path",
|
||
"required": true,
|
||
"schema": {
|
||
"type": "string",
|
||
"title": "Uuid"
|
||
}
|
||
},
|
||
{
|
||
"name": "cascade",
|
||
"in": "query",
|
||
"required": false,
|
||
"schema": {
|
||
"type": "boolean",
|
||
"default": false,
|
||
"title": "Cascade"
|
||
}
|
||
},
|
||
{
|
||
"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.\n\nIf no readings exist yet, returns ``{\"found\": false, \"recorded_at\": null,\n\"payload\": null}`` (200, not 404) so the front-end can distinguish\n\"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.\n\nWhen the window contains more rows than ``limit``, the **most recent** N rows\nare returned (``ORDER BY recorded_at DESC LIMIT n``), then reversed to\nascending order before being sent to the client. This ensures that for long\ntime-range requests (e.g. 6 h / 24 h) the caller always sees the latest data\nrather than the oldest segment of the window.\n\nWhen the window has fewer rows than ``limit`` the full window is returned in\nascending order — behaviour is identical to a plain ascending query.\n\nThe response schema is unchanged: items are always ``recorded_at`` ascending.\n\nThe query uses the ``(device_id, recorded_at)`` composite index for\nefficient time-window scans.\n\nQuery parameters:\n- ``start``: inclusive lower bound (ISO8601 datetime)\n- ``end``: inclusive upper bound (ISO8601 datetime)\n- ``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.\n\nEach entry has ``key``, ``label`` (derived from key if the profile has\nnone — underscores → spaces, title-cased), ``unit``, and ``device_class``.\nThis is the authoritative metadata source for front-end card labels and\nchart 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.\n\nUseful for validating gateway connectivity and Modbus address settings\nbefore relying on the background polling job.\n\nThis is a write-class endpoint (it initiates network I/O on demand) and\ntherefore 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.\n\nOn success, sets an HttpOnly session cookie and returns the session user + CSRF token.\nOn failure, returns 401 with no cookie set.\nRepeated failures trigger exponential back-off (429 + Retry-After).\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"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"/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.\n\nThe secret is stored in the DB but TOTP is NOT yet enabled (totp_enabled stays\nFalse until the user confirms with POST /api/auth/totp/enable).\n\nRecovery codes are returned here as plaintext exactly once; their Argon2 hashes\nare persisted immediately so enable only needs to flip the enabled flag.\n\nRepeating this call replaces any prior pending secret and regenerates codes.\n\nRequires: 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.\n\nRequires a prior call to POST /api/auth/totp/setup (so that a pending secret\nexists). On success, totp_enabled becomes True.\n\nReturns 400 if the code is wrong or there is no pending secret.\nRequires: 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:\n- ``password``: the user's current login password, OR\n- ``code``: the current 6-digit TOTP code.\n\nOn success: totp_enabled=False, totp_secret cleared, all recovery codes deleted.\nReturns 400 if neither credential matches or neither is provided.\nRequires: 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.\n\nResponse contains only ``{\"enabled\": bool}``.\nSecret and recovery codes are NEVER returned here.\nRequires: 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"
|
||
},
|
||
"ContractCreate": {
|
||
"properties": {
|
||
"name": {
|
||
"type": "string",
|
||
"maxLength": 255,
|
||
"minLength": 1,
|
||
"title": "Name"
|
||
},
|
||
"kind": {
|
||
"type": "string",
|
||
"maxLength": 32,
|
||
"minLength": 1,
|
||
"title": "Kind"
|
||
},
|
||
"currency": {
|
||
"type": "string",
|
||
"maxLength": 8,
|
||
"minLength": 1,
|
||
"title": "Currency",
|
||
"default": "EUR"
|
||
},
|
||
"values": {
|
||
"additionalProperties": true,
|
||
"type": "object",
|
||
"title": "Values"
|
||
},
|
||
"effective_from": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Effective From",
|
||
"description": "UTC datetime from which the first pricing version is effective. Defaults to the current UTC time when omitted."
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"name",
|
||
"kind",
|
||
"values"
|
||
],
|
||
"title": "ContractCreate",
|
||
"description": "Request body for POST /api/energy/contracts.\n\n``effective_from`` defaults to the current UTC time if not provided,\ngiving the first version an open-ended start from \"now\".\n``kind`` is validated at the application layer against the profile registry;\nclients should send ``\"manual\"`` or ``\"tibber\"``."
|
||
},
|
||
"ContractDetailResponse": {
|
||
"properties": {
|
||
"id": {
|
||
"type": "integer",
|
||
"title": "Id"
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"title": "Name"
|
||
},
|
||
"kind": {
|
||
"type": "string",
|
||
"title": "Kind"
|
||
},
|
||
"active": {
|
||
"type": "boolean",
|
||
"title": "Active"
|
||
},
|
||
"currency": {
|
||
"type": "string",
|
||
"title": "Currency"
|
||
},
|
||
"created_at": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"title": "Created At"
|
||
},
|
||
"updated_at": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"title": "Updated At"
|
||
},
|
||
"versions": {
|
||
"items": {
|
||
"$ref": "#/components/schemas/ContractVersionResponse"
|
||
},
|
||
"type": "array",
|
||
"title": "Versions"
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"id",
|
||
"name",
|
||
"kind",
|
||
"active",
|
||
"currency",
|
||
"created_at",
|
||
"updated_at",
|
||
"versions"
|
||
],
|
||
"title": "ContractDetailResponse",
|
||
"description": "Response schema for a single EnergyContract with full version history.\n\nReturned by GET /api/energy/contracts/{id} and by successful POST / PATCH\noperations where the caller needs to see all version data."
|
||
},
|
||
"ContractListResponse": {
|
||
"properties": {
|
||
"items": {
|
||
"items": {
|
||
"$ref": "#/components/schemas/ContractResponse"
|
||
},
|
||
"type": "array",
|
||
"title": "Items"
|
||
},
|
||
"total": {
|
||
"type": "integer",
|
||
"title": "Total"
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"items",
|
||
"total"
|
||
],
|
||
"title": "ContractListResponse",
|
||
"description": "Response schema for GET /api/energy/contracts."
|
||
},
|
||
"ContractPatch": {
|
||
"properties": {
|
||
"name": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"maxLength": 255,
|
||
"minLength": 1
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Name"
|
||
},
|
||
"active": {
|
||
"anyOf": [
|
||
{
|
||
"type": "boolean"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Active"
|
||
}
|
||
},
|
||
"type": "object",
|
||
"title": "ContractPatch",
|
||
"description": "Request body for PATCH /api/energy/contracts/{id}.\n\nAll fields are optional. Sending ``active=true`` activates this contract\n(deactivating all others); ``active=false`` deactivates it without affecting\nother contracts."
|
||
},
|
||
"ContractResponse": {
|
||
"properties": {
|
||
"id": {
|
||
"type": "integer",
|
||
"title": "Id"
|
||
},
|
||
"name": {
|
||
"type": "string",
|
||
"title": "Name"
|
||
},
|
||
"kind": {
|
||
"type": "string",
|
||
"title": "Kind"
|
||
},
|
||
"active": {
|
||
"type": "boolean",
|
||
"title": "Active"
|
||
},
|
||
"currency": {
|
||
"type": "string",
|
||
"title": "Currency"
|
||
},
|
||
"created_at": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"title": "Created At"
|
||
},
|
||
"updated_at": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"title": "Updated At"
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"id",
|
||
"name",
|
||
"kind",
|
||
"active",
|
||
"currency",
|
||
"created_at",
|
||
"updated_at"
|
||
],
|
||
"title": "ContractResponse",
|
||
"description": "Response schema for a single EnergyContract (without embedded versions).\n\nUsed for list responses where embedding all versions would be expensive."
|
||
},
|
||
"ContractVersionResponse": {
|
||
"properties": {
|
||
"id": {
|
||
"type": "integer",
|
||
"title": "Id"
|
||
},
|
||
"effective_from": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"title": "Effective From"
|
||
},
|
||
"effective_to": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Effective To"
|
||
},
|
||
"values": {
|
||
"additionalProperties": true,
|
||
"type": "object",
|
||
"title": "Values"
|
||
},
|
||
"created_at": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"title": "Created At"
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"id",
|
||
"effective_from",
|
||
"effective_to",
|
||
"values",
|
||
"created_at"
|
||
],
|
||
"title": "ContractVersionResponse",
|
||
"description": "Response schema for a single EnergyContractVersion row."
|
||
},
|
||
"CostPeriodSchema": {
|
||
"properties": {
|
||
"period_start": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"title": "Period Start"
|
||
},
|
||
"d1_kwh": {
|
||
"type": "number",
|
||
"title": "D1 Kwh",
|
||
"description": "Delivered low-tariff kWh for this period."
|
||
},
|
||
"d2_kwh": {
|
||
"type": "number",
|
||
"title": "D2 Kwh",
|
||
"description": "Delivered normal-tariff kWh for this period."
|
||
},
|
||
"r1_kwh": {
|
||
"type": "number",
|
||
"title": "R1 Kwh",
|
||
"description": "Returned low-tariff kWh for this period."
|
||
},
|
||
"r2_kwh": {
|
||
"type": "number",
|
||
"title": "R2 Kwh",
|
||
"description": "Returned normal-tariff kWh for this period."
|
||
},
|
||
"import_cost": {
|
||
"type": "number",
|
||
"title": "Import Cost",
|
||
"description": "Cost of electricity drawn from grid (EUR)."
|
||
},
|
||
"export_revenue": {
|
||
"type": "number",
|
||
"title": "Export Revenue",
|
||
"description": "Revenue from electricity fed to grid (EUR)."
|
||
},
|
||
"net_cost": {
|
||
"type": "number",
|
||
"title": "Net Cost",
|
||
"description": "import_cost − export_revenue (EUR)."
|
||
},
|
||
"currency": {
|
||
"type": "string",
|
||
"title": "Currency",
|
||
"description": "ISO 4217 currency code."
|
||
},
|
||
"degraded": {
|
||
"type": "boolean",
|
||
"title": "Degraded",
|
||
"description": "True when the period was computed with incomplete data."
|
||
},
|
||
"contract_version_id": {
|
||
"anyOf": [
|
||
{
|
||
"type": "integer"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Contract Version Id",
|
||
"description": "FK to the contract version used for this billing period (null when degraded)."
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"period_start",
|
||
"d1_kwh",
|
||
"d2_kwh",
|
||
"r1_kwh",
|
||
"r2_kwh",
|
||
"import_cost",
|
||
"export_revenue",
|
||
"net_cost",
|
||
"currency",
|
||
"degraded"
|
||
],
|
||
"title": "CostPeriodSchema",
|
||
"description": "One 15-minute billing record from the energy_cost_period table."
|
||
},
|
||
"CostsResponse": {
|
||
"properties": {
|
||
"items": {
|
||
"items": {
|
||
"$ref": "#/components/schemas/CostPeriodSchema"
|
||
},
|
||
"type": "array",
|
||
"title": "Items"
|
||
},
|
||
"total": {
|
||
"type": "integer",
|
||
"title": "Total",
|
||
"description": "Number of items returned."
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"items",
|
||
"total"
|
||
],
|
||
"title": "CostsResponse",
|
||
"description": "Response for GET /api/energy/costs."
|
||
},
|
||
"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."
|
||
},
|
||
"DsmrLatestResponse": {
|
||
"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"
|
||
],
|
||
"title": "DsmrLatestResponse",
|
||
"description": "Response for GET /api/energy/dsmr/latest.\n\n``found`` is False when no dsmr_reading rows exist yet. The front-end\nshould check ``found`` before reading ``recorded_at`` or ``payload``."
|
||
},
|
||
"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.\n\n``value_getter`` is intentionally excluded — it is a non-serialisable\ncallable 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.\n\n``toggles`` is a map from entity key to desired enabled state (bool).\nOnly 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"
|
||
},
|
||
"ManualTariffSchema": {
|
||
"properties": {
|
||
"buy_dal": {
|
||
"type": "number",
|
||
"title": "Buy Dal",
|
||
"description": "Effective buy price, low-tariff / dal (EUR/kWh)."
|
||
},
|
||
"buy_normal": {
|
||
"type": "number",
|
||
"title": "Buy Normal",
|
||
"description": "Effective buy price, normal / high-tariff (EUR/kWh)."
|
||
},
|
||
"sell_dal": {
|
||
"type": "number",
|
||
"title": "Sell Dal",
|
||
"description": "Sell price, low-tariff / dal (EUR/kWh)."
|
||
},
|
||
"sell_normal": {
|
||
"type": "number",
|
||
"title": "Sell Normal",
|
||
"description": "Sell price, normal / high-tariff (EUR/kWh)."
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"buy_dal",
|
||
"buy_normal",
|
||
"sell_dal",
|
||
"sell_normal"
|
||
],
|
||
"title": "ManualTariffSchema",
|
||
"description": "Fixed-tariff breakdown for manual contracts.\n\nPrices are the *effective* buy prices as used by the billing engine\n(energy_buy_x + energy_tax + ode) and the raw sell prices."
|
||
},
|
||
"MeterDeclareRequest": {
|
||
"properties": {
|
||
"label": {
|
||
"type": "string",
|
||
"maxLength": 255,
|
||
"minLength": 1,
|
||
"title": "Label"
|
||
},
|
||
"started_at": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"title": "Started At",
|
||
"description": "UTC (or server-local naive) datetime from which this meter epoch starts. May be in the past (retroactive declaration)."
|
||
},
|
||
"reason": {
|
||
"$ref": "#/components/schemas/MeterReason",
|
||
"description": "Why this epoch was created. One of: initial, meter_swap, home_move, other."
|
||
},
|
||
"note": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"maxLength": 1024
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Note"
|
||
},
|
||
"commodity": {
|
||
"type": "string",
|
||
"maxLength": 32,
|
||
"minLength": 1,
|
||
"title": "Commodity",
|
||
"description": "Energy commodity this meter measures. Defaults to 'electricity'.",
|
||
"default": "electricity"
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"label",
|
||
"started_at",
|
||
"reason"
|
||
],
|
||
"title": "MeterDeclareRequest",
|
||
"description": "Request body for POST /api/energy/meters.\n\nDeclares a new meter epoch (swap, home move, or initial declaration). The\nservice layer closes the current active meter for the given commodity at\n``started_at`` and opens a new one.\n\n``started_at`` follows the Principle-A localisation convention: a\ntimezone-naive value is interpreted as the **server's local wall-clock time**\n(e.g. CEST midnight → stored as UTC the night before); a timezone-aware\nvalue is converted to UTC as-is. Omitting ``started_at`` is not allowed —\nevery meter declaration must carry an explicit start timestamp.\n\n``commodity`` defaults to ``\"electricity\"``; the field is available for\nfuture use with ``gas`` or ``heating``."
|
||
},
|
||
"MeterListResponse": {
|
||
"properties": {
|
||
"items": {
|
||
"items": {
|
||
"$ref": "#/components/schemas/MeterResponse"
|
||
},
|
||
"type": "array",
|
||
"title": "Items"
|
||
},
|
||
"total": {
|
||
"type": "integer",
|
||
"title": "Total"
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"items",
|
||
"total"
|
||
],
|
||
"title": "MeterListResponse",
|
||
"description": "Response schema for GET /api/energy/meters.\n\nMeters are returned in ascending ``started_at`` order so the caller sees\nthe historical installation sequence."
|
||
},
|
||
"MeterPatchRequest": {
|
||
"properties": {
|
||
"label": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"maxLength": 255,
|
||
"minLength": 1
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Label"
|
||
},
|
||
"note": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"maxLength": 1024
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Note"
|
||
},
|
||
"started_at": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Started At",
|
||
"description": "Retroactive correction of the meter epoch start timestamp. Triggers billing recompute over the affected window."
|
||
}
|
||
},
|
||
"type": "object",
|
||
"title": "MeterPatchRequest",
|
||
"description": "Request body for PATCH /api/energy/meters/{id}.\n\nAll fields are optional. Only non-``None`` values are applied.\n\nUpdating ``started_at`` is a **retroactive correction**: the service layer\nmaintains timeline continuity (adjusting the preceding meter's ``ended_at``)\nand the API layer triggers ``recompute_range`` over the affected window so\nthat billing attribution is re-judged."
|
||
},
|
||
"MeterReason": {
|
||
"type": "string",
|
||
"enum": [
|
||
"initial",
|
||
"meter_swap",
|
||
"home_move",
|
||
"other"
|
||
],
|
||
"title": "MeterReason",
|
||
"description": "Allowed values for the meter epoch creation reason."
|
||
},
|
||
"MeterResponse": {
|
||
"properties": {
|
||
"id": {
|
||
"type": "integer",
|
||
"title": "Id"
|
||
},
|
||
"label": {
|
||
"type": "string",
|
||
"title": "Label"
|
||
},
|
||
"commodity": {
|
||
"type": "string",
|
||
"title": "Commodity"
|
||
},
|
||
"started_at": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"title": "Started At"
|
||
},
|
||
"ended_at": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string",
|
||
"format": "date-time"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Ended At"
|
||
},
|
||
"reason": {
|
||
"type": "string",
|
||
"title": "Reason"
|
||
},
|
||
"note": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Note"
|
||
},
|
||
"created_at": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"title": "Created At"
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"id",
|
||
"label",
|
||
"commodity",
|
||
"started_at",
|
||
"ended_at",
|
||
"reason",
|
||
"note",
|
||
"created_at"
|
||
],
|
||
"title": "MeterResponse",
|
||
"description": "Response schema for a single Meter epoch row.\n\n``ended_at`` is ``null`` for the currently active meter."
|
||
},
|
||
"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.\n\n``found`` is False and ``recorded_at``/``payload`` are None when the device\nhas 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.\n\nOn success ``ok=True`` and ``payload`` contains the decoded values.\nOn 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."
|
||
},
|
||
"PricePointSchema": {
|
||
"properties": {
|
||
"starts_at": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"title": "Starts At"
|
||
},
|
||
"buy": {
|
||
"type": "number",
|
||
"title": "Buy",
|
||
"description": "All-in buy price in EUR/kWh (including taxes)."
|
||
},
|
||
"sell": {
|
||
"type": "number",
|
||
"title": "Sell",
|
||
"description": "Net sell price in EUR/kWh."
|
||
},
|
||
"level": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Level",
|
||
"description": "Tibber price level (CHEAP / NORMAL / EXPENSIVE); null for manual."
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"starts_at",
|
||
"buy",
|
||
"sell"
|
||
],
|
||
"title": "PricePointSchema",
|
||
"description": "A single 15-minute price point (tibber) or placeholder entry."
|
||
},
|
||
"PricesResponse": {
|
||
"properties": {
|
||
"kind": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Kind",
|
||
"description": "Active contract kind ('tibber' or 'manual'), or null if no active contract."
|
||
},
|
||
"currency": {
|
||
"type": "string",
|
||
"title": "Currency",
|
||
"description": "ISO 4217 currency code."
|
||
},
|
||
"points": {
|
||
"items": {
|
||
"$ref": "#/components/schemas/PricePointSchema"
|
||
},
|
||
"type": "array",
|
||
"title": "Points",
|
||
"description": "15-minute price points for tibber contracts (ascending by starts_at). Empty for manual contracts or when no active contract exists."
|
||
},
|
||
"tariff": {
|
||
"anyOf": [
|
||
{
|
||
"$ref": "#/components/schemas/ManualTariffSchema"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"description": "Fixed tariff table for manual contracts. Null for tibber contracts and when no active contract exists."
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"kind",
|
||
"currency",
|
||
"points"
|
||
],
|
||
"title": "PricesResponse",
|
||
"description": "Response for GET /api/energy/prices.\n\n``kind`` mirrors the active contract kind:\n- ``\"tibber\"`` → ``points`` has actual 15-min price entries; ``tariff`` is null.\n- ``\"manual\"`` → ``points`` is empty; ``tariff`` carries the fixed-rate table.\n- ``None`` → no active contract; both ``points`` and ``tariff`` are empty/null.\n\n``currency`` comes from the active contract (or \"EUR\" fallback).\n``points`` is always ascending by ``starts_at``."
|
||
},
|
||
"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."
|
||
},
|
||
"ProfilesResponse": {
|
||
"properties": {
|
||
"profiles": {
|
||
"items": {
|
||
"additionalProperties": true,
|
||
"type": "object"
|
||
},
|
||
"type": "array",
|
||
"title": "Profiles"
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"profiles"
|
||
],
|
||
"title": "ProfilesResponse",
|
||
"description": "Response schema for GET /api/energy/profiles.\n\n``profiles`` is a list of raw profile dicts as produced by\n``list_profiles()`` (Pydantic model dumps). Each entry contains at minimum\n``kind`` and ``label``; the full nested structure allows the front-end to\nrender a type-appropriate form for each pricing profile."
|
||
},
|
||
"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"
|
||
},
|
||
"RecomputeResponse": {
|
||
"properties": {
|
||
"recomputed": {
|
||
"type": "integer",
|
||
"title": "Recomputed",
|
||
"description": "Number of 15-minute periods for which a billing record was written (inserted or updated). Periods skipped due to missing contract or missing Tibber price are not counted."
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"recomputed"
|
||
],
|
||
"title": "RecomputeResponse",
|
||
"description": "Response for POST /api/energy/costs/recompute."
|
||
},
|
||
"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"
|
||
},
|
||
"SummaryResponse": {
|
||
"properties": {
|
||
"currency": {
|
||
"type": "string",
|
||
"title": "Currency"
|
||
},
|
||
"metered_import": {
|
||
"type": "number",
|
||
"title": "Metered Import",
|
||
"description": "Σ import_cost for non-degraded periods."
|
||
},
|
||
"metered_export": {
|
||
"type": "number",
|
||
"title": "Metered Export",
|
||
"description": "Σ export_revenue for non-degraded periods."
|
||
},
|
||
"metered_net": {
|
||
"type": "number",
|
||
"title": "Metered Net",
|
||
"description": "Σ net_cost for non-degraded periods."
|
||
},
|
||
"fixed_costs": {
|
||
"type": "number",
|
||
"title": "Fixed Costs",
|
||
"description": "Standing charges (network_fee + management_fee) apportioned over the interval."
|
||
},
|
||
"credits": {
|
||
"type": "number",
|
||
"title": "Credits",
|
||
"description": "Energy-tax credit (heffingskorting) apportioned over the interval."
|
||
},
|
||
"total_payable": {
|
||
"type": "number",
|
||
"title": "Total Payable",
|
||
"description": "metered_net + fixed_costs − credits (actual amount owed)."
|
||
},
|
||
"period_count": {
|
||
"type": "integer",
|
||
"title": "Period Count",
|
||
"description": "Number of non-degraded billing periods in range."
|
||
},
|
||
"degraded_count": {
|
||
"type": "integer",
|
||
"title": "Degraded Count",
|
||
"description": "Number of degraded billing periods in range."
|
||
},
|
||
"days": {
|
||
"type": "number",
|
||
"title": "Days",
|
||
"description": "Interval length in days."
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"currency",
|
||
"metered_import",
|
||
"metered_export",
|
||
"metered_net",
|
||
"fixed_costs",
|
||
"credits",
|
||
"total_payable",
|
||
"period_count",
|
||
"degraded_count",
|
||
"days"
|
||
],
|
||
"title": "SummaryResponse",
|
||
"description": "Response for GET /api/energy/costs/summary.\n\nAll monetary values are in ``currency``.\n\n``total_payable = metered_net + fixed_costs − credits``"
|
||
},
|
||
"TibberTestPriceSchema": {
|
||
"properties": {
|
||
"starts_at": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"title": "Starts At"
|
||
},
|
||
"total": {
|
||
"type": "number",
|
||
"title": "Total"
|
||
},
|
||
"energy": {
|
||
"type": "number",
|
||
"title": "Energy"
|
||
},
|
||
"tax": {
|
||
"type": "number",
|
||
"title": "Tax"
|
||
},
|
||
"currency": {
|
||
"type": "string",
|
||
"title": "Currency"
|
||
},
|
||
"level": {
|
||
"anyOf": [
|
||
{
|
||
"type": "string"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
],
|
||
"title": "Level"
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"starts_at",
|
||
"total",
|
||
"energy",
|
||
"tax",
|
||
"currency"
|
||
],
|
||
"title": "TibberTestPriceSchema",
|
||
"description": "Current Tibber price point returned on a successful test.\n\nCarries enough fields for the front-end to confirm the API is working and\ndisplay the live price. The API token is **never** included."
|
||
},
|
||
"TibberTestResponse": {
|
||
"properties": {
|
||
"result": {
|
||
"type": "string",
|
||
"enum": [
|
||
"success",
|
||
"config-error",
|
||
"failed"
|
||
],
|
||
"title": "Result"
|
||
},
|
||
"message": {
|
||
"type": "string",
|
||
"title": "Message"
|
||
},
|
||
"price": {
|
||
"anyOf": [
|
||
{
|
||
"$ref": "#/components/schemas/TibberTestPriceSchema"
|
||
},
|
||
{
|
||
"type": "null"
|
||
}
|
||
]
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"result",
|
||
"message"
|
||
],
|
||
"title": "TibberTestResponse",
|
||
"description": "Three-state response for POST /api/energy/tibber/test.\n\nPossible ``result`` values:\n\n- ``\"success\"`` — Tibber API responded with a valid price.\n- ``\"config-error\"`` — Token is missing or not configured.\n- ``\"failed\"`` — API call failed (auth rejected, network error, timeout, etc.).\n\n``price`` is populated only on ``\"success\"``; it is null otherwise.\n``message`` always contains a human-readable explanation."
|
||
},
|
||
"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.\n\nExactly 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.\n\n``secret`` and ``recovery_codes`` are **one-time plaintext values**.\nThey are never returned again by any subsequent API call.\nThe 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"
|
||
},
|
||
"VersionCreate": {
|
||
"properties": {
|
||
"effective_from": {
|
||
"type": "string",
|
||
"format": "date-time",
|
||
"title": "Effective From"
|
||
},
|
||
"values": {
|
||
"additionalProperties": true,
|
||
"type": "object",
|
||
"title": "Values"
|
||
}
|
||
},
|
||
"type": "object",
|
||
"required": [
|
||
"effective_from",
|
||
"values"
|
||
],
|
||
"title": "VersionCreate",
|
||
"description": "Request body for POST /api/energy/contracts/{id}/versions."
|
||
}
|
||
}
|
||
}
|
||
} |