Files
home-automation/openapi/openapi.json
T

5009 lines
146 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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 response includes the count of\nrecomputed periods in ``recomputed_periods`` (not part of ``MeterResponse``\n— the recompute is transparent; callers should re-fetch costs if needed).",
"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."
}
}
}
}