Tibber's API `total` already includes the buy-side inkoopvergoeding
(verified from production data: total = spot×1.21 + energy_tax 0.11085 +
inkoopvergoeding 0.0248). Under net metering Tibber pays back
`total − verkoopvergoeding` per returned kWh (NL: EUR 0.28 -> 0.2552), so the
two EUR 0.0248 fees do NOT cancel — the feed-in price sits 0.0248 below buy.
Model the verkoopvergoeding as a first-class, always-subtracted contract
field `energy.sell_fee` (default 0.0248) instead of folding it into
`sell_adjust`. New sell formula:
sell = total − energy_tax − sell_fee − sell_adjust
`sell_adjust` now carries only the net-metering energy-tax refund
(= −energy_tax). Applied in both the billing strategy and the /prices
endpoint; recorded in the pricing snapshot. Frontend renders the field
automatically (dynamic profile form). Docs (references, m6) corrected to
drop the wrong "fees cancel" premise.
3893 lines
106 KiB
YAML
3893 lines
106 KiB
YAML
openapi: 3.1.0
|
||
info:
|
||
title: Home Automation Backend (Python)
|
||
description: Home automation backend with auth, runtime config, Home Assistant integrations,
|
||
TickTick integration, and SQLite-backed recorders.
|
||
version: 0.1.0
|
||
paths:
|
||
/status:
|
||
get:
|
||
tags:
|
||
- system
|
||
summary: Get Status
|
||
operationId: get_status_status_get
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/StatusResponse'
|
||
/api/config:
|
||
get:
|
||
tags:
|
||
- api-config
|
||
summary: Get Config
|
||
description: Return all configuration sections. Secret field values are masked
|
||
(empty string).
|
||
operationId: get_config_api_config_get
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ConfigResponse'
|
||
put:
|
||
tags:
|
||
- api-config
|
||
summary: Put Config
|
||
description: 'Save configuration updates.
|
||
|
||
|
||
- Blank secret value keeps the existing stored value (no change).
|
||
|
||
- Invalid values return 422 and nothing is written to the database.
|
||
|
||
- If MQTT-related settings changed, the MQTT client reconnects automatically.'
|
||
operationId: put_config_api_config_put
|
||
parameters:
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ConfigUpdateRequest'
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ConfigUpdateResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/config/smtp/test:
|
||
post:
|
||
tags:
|
||
- api-config
|
||
summary: Post Smtp Test
|
||
description: 'Send a test SMTP email using the current runtime settings.
|
||
|
||
|
||
Returns a structured result indicating success or the category of failure.
|
||
|
||
Three possible outcomes:
|
||
|
||
- 200 { "result": "success", "message": ... }
|
||
|
||
- 400 { "result": "config-error", "message": ... } (EmailConfigurationError)
|
||
|
||
- 502 { "result": "failed", "message": ... } (EmailDeliveryError)
|
||
|
||
|
||
SMTP credentials are never echoed in the response.'
|
||
operationId: post_smtp_test_api_config_smtp_test_post
|
||
parameters:
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SmtpTestResponse'
|
||
'400':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SmtpTestResponse'
|
||
description: Bad Request
|
||
'502':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SmtpTestResponse'
|
||
description: Bad Gateway
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/config/mqtt/test:
|
||
post:
|
||
tags:
|
||
- api-config
|
||
summary: Post Mqtt Test
|
||
description: 'Test MQTT broker connectivity by attempting to connect and publishing
|
||
a
|
||
|
||
test message to ``<ha_discovery_prefix>/home-automation/test``.
|
||
|
||
|
||
The message is visible in MQTT Explorer (or any subscriber) so users can
|
||
|
||
confirm the full broker publish path is working.
|
||
|
||
|
||
Three possible outcomes:
|
||
|
||
- 200 { "result": "success", "message": ... }
|
||
|
||
- 400 { "result": "config-error", "message": ... } (not configured)
|
||
|
||
- 502 { "result": "failed", "message": ... } (connection/publish
|
||
error)
|
||
|
||
|
||
MQTT credentials are never echoed in the response.'
|
||
operationId: post_mqtt_test_api_config_mqtt_test_post
|
||
parameters:
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MqttTestResponse'
|
||
'400':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MqttTestResponse'
|
||
description: Bad Request
|
||
'502':
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MqttTestResponse'
|
||
description: Bad Gateway
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/locations:
|
||
get:
|
||
tags:
|
||
- api-data
|
||
summary: Get Locations
|
||
description: 'Return location records with optional time-window filtering and
|
||
pagination.
|
||
|
||
|
||
- ``start`` / ``end`` are ISO8601 strings; filtering is **inclusive** on both
|
||
bounds.
|
||
|
||
- Results are ordered by ``datetime`` ascending.
|
||
|
||
- ``limit`` is capped at 5000 to prevent full-table exports.'
|
||
operationId: get_locations_api_locations_get
|
||
parameters:
|
||
- name: limit
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
maximum: 5000
|
||
minimum: 1
|
||
default: 1000
|
||
title: Limit
|
||
- name: offset
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
minimum: 0
|
||
default: 0
|
||
title: Offset
|
||
- name: start
|
||
in: query
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: Start
|
||
- name: end
|
||
in: query
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: End
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/LocationsResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/poo:
|
||
get:
|
||
tags:
|
||
- api-data
|
||
summary: Get Poo
|
||
description: 'Return poo records ordered by timestamp descending (most recent
|
||
first).
|
||
|
||
|
||
``limit`` is capped at 1000 to prevent full-table exports.'
|
||
operationId: get_poo_api_poo_get
|
||
parameters:
|
||
- name: limit
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
maximum: 1000
|
||
minimum: 1
|
||
default: 100
|
||
title: Limit
|
||
- name: offset
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
minimum: 0
|
||
default: 0
|
||
title: Offset
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PooResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/public-ip:
|
||
get:
|
||
tags:
|
||
- api-data
|
||
summary: Get Public Ip
|
||
description: 'Return the current public IP state and recent history.
|
||
|
||
|
||
- ``state`` is ``null`` if no IP check has been performed yet.
|
||
|
||
- ``history`` is ordered by ``observed_at`` descending (most recent first).
|
||
|
||
- ``limit`` applies to the history list and is capped at 1000.'
|
||
operationId: get_public_ip_api_public_ip_get
|
||
parameters:
|
||
- name: limit
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
maximum: 1000
|
||
minimum: 1
|
||
default: 100
|
||
title: Limit
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PublicIPResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/locations/{person}/{datetime}:
|
||
patch:
|
||
tags:
|
||
- api-data
|
||
summary: Patch Location
|
||
description: 'Update the non-PK fields of a single location record.
|
||
|
||
|
||
- ``person`` and ``datetime`` identify the row (composite PK) and are immutable.
|
||
|
||
- Only ``latitude``, ``longitude``, and ``altitude`` may be updated.
|
||
|
||
- Omitted body fields are left unchanged.
|
||
|
||
- Returns **404** if the PK does not exist.'
|
||
operationId: patch_location_api_locations__person___datetime__patch
|
||
parameters:
|
||
- name: person
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
title: Person
|
||
- name: datetime
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
title: Datetime
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
requestBody:
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/LocationUpdateRequest'
|
||
default: {}
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/LocationRecord'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
delete:
|
||
tags:
|
||
- api-data
|
||
summary: Delete Location Record
|
||
description: 'Delete the single location record identified by its composite
|
||
PK.
|
||
|
||
|
||
- Exactly one row is deleted; **404** if the PK does not exist.
|
||
|
||
- No batch delete / truncate path is available.'
|
||
operationId: delete_location_record_api_locations__person___datetime__delete
|
||
parameters:
|
||
- name: person
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
title: Person
|
||
- name: datetime
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
title: Datetime
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
responses:
|
||
'204':
|
||
description: Successful Response
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/poo/{timestamp}:
|
||
patch:
|
||
tags:
|
||
- api-data
|
||
summary: Patch Poo
|
||
description: 'Update the non-PK fields of a single poo record.
|
||
|
||
|
||
- ``timestamp`` is the PK and is immutable.
|
||
|
||
- Only ``status``, ``latitude``, and ``longitude`` may be updated.
|
||
|
||
- Omitted body fields are left unchanged.
|
||
|
||
- Returns **404** if the PK does not exist.'
|
||
operationId: patch_poo_api_poo__timestamp__patch
|
||
parameters:
|
||
- name: timestamp
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
title: Timestamp
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
requestBody:
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PooUpdateRequest'
|
||
default: {}
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PooRecord'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
delete:
|
||
tags:
|
||
- api-data
|
||
summary: Delete Poo
|
||
description: 'Delete the single poo record identified by its PK.
|
||
|
||
|
||
- Exactly one row is deleted; **404** if the PK does not exist.
|
||
|
||
- No batch delete / truncate path is available.'
|
||
operationId: delete_poo_api_poo__timestamp__delete
|
||
parameters:
|
||
- name: timestamp
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
title: Timestamp
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
responses:
|
||
'204':
|
||
description: Successful Response
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/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_fee − 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.
|
||
|
||
|
||
Rows are ordered by ``period_start`` ascending. When the window contains
|
||
|
||
more rows than ``limit``, the **most recent** N rows are returned (DESC LIMIT),
|
||
|
||
then reversed to ascending order — identical to the modbus readings pattern.
|
||
|
||
|
||
Query parameters:
|
||
|
||
- ``start``: inclusive lower bound on ``period_start`` (ISO 8601 datetime).
|
||
|
||
- ``end``: inclusive upper bound on ``period_start`` (ISO 8601 datetime).
|
||
|
||
- ``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\n\
|
||
apportioned 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.
|
||
|
||
|
||
Returns ``{"found": false, "recorded_at": null, "payload": null}`` (200, not
|
||
|
||
404) when no rows exist yet, so the front-end can distinguish "no data" from
|
||
|
||
a 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.
|
||
|
||
|
||
Calls ``energy_cost.recompute_range(session, start, end)`` which overwrites
|
||
|
||
existing rows (including successful ones) for every UTC quarter-hour boundary
|
||
|
||
in ``[start, end)``.
|
||
|
||
|
||
**Idempotency**: repeated calls with the same window produce the same
|
||
|
||
outcome. No rows are deleted; only upserted.
|
||
|
||
|
||
**Window constraint**: the maximum allowed range is {_RECOMPUTE_MAX_DAYS}
|
||
days.
|
||
|
||
Requests exceeding this return 422.
|
||
|
||
|
||
Returns the number of periods for which a billing record was written.
|
||
|
||
Periods 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.
|
||
|
||
|
||
Returns the full profile structure for each supported contract kind
|
||
|
||
(``manual`` and ``tibber``). The front-end uses this to dynamically
|
||
|
||
render 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.
|
||
|
||
|
||
Returns a flat list (no embedded version history); use
|
||
|
||
GET /api/energy/contracts/{id} to fetch the full version history for a
|
||
|
||
specific 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.
|
||
|
||
|
||
The ``values`` dict is validated against the YAML profile for the given
|
||
|
||
``kind``; non-conforming values result in 422 Unprocessable Entity.
|
||
|
||
The new contract is created with ``active=False``; use
|
||
|
||
PATCH /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.
|
||
|
||
|
||
Versions are ordered by ``effective_from`` ascending so the caller can
|
||
|
||
easily 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.
|
||
|
||
|
||
- ``name``: updates the human-readable label.
|
||
|
||
- ``active=true``: activates this contract (all others are deactivated).
|
||
|
||
- ``active=false``: deactivates this contract (no effect on others).
|
||
|
||
|
||
At most one contract may be active at any time; the service layer enforces
|
||
|
||
mutual 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.
|
||
|
||
|
||
This is how price changes are recorded: the current open version is
|
||
|
||
automatically closed (its ``effective_to`` is set to ``body.effective_from``)
|
||
|
||
and a new version is created starting at ``body.effective_from``.
|
||
|
||
|
||
The ``values`` dict must conform to the contract''s pricing profile.
|
||
|
||
Non-conforming values return 422. If ``effective_from`` is not strictly
|
||
|
||
after the previous version''s ``effective_from``, 422 is returned without
|
||
|
||
writing any rows.
|
||
|
||
|
||
Historical 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.
|
||
|
||
|
||
Returns the full historical sequence of meter installations across all
|
||
|
||
commodities. The active meter (``ended_at=null``) appears last because it
|
||
|
||
has 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).
|
||
|
||
|
||
Closes the current active meter for the given commodity at ``started_at``
|
||
|
||
and opens a new active meter. If no active meter exists, the new meter is
|
||
|
||
simply created without closing anything.
|
||
|
||
|
||
**Validation**: ``started_at`` must be **≥** the current active meter''s
|
||
|
||
own ``started_at`` (no chronological backdate below the active epoch''s
|
||
|
||
start). Equal timestamps are allowed (replaces the current meter at the
|
||
|
||
same logical moment). Violation → 422.
|
||
|
||
|
||
**Retroactive recompute**: if ``started_at`` is in the past, billing
|
||
|
||
records from that point forward are re-judged via ``recompute_range`` to
|
||
|
||
reflect the new meter attribution. The recompute is transparent — the
|
||
|
||
response body is the created meter (``MeterResponse``) only and does **not**
|
||
|
||
include a recompute count; callers should re-fetch costs if they need the
|
||
|
||
updated totals.'
|
||
operationId: declare_energy_meter_api_energy_meters_post
|
||
parameters:
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MeterDeclareRequest'
|
||
responses:
|
||
'201':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MeterResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/energy/meters/{meter_id}:
|
||
patch:
|
||
tags:
|
||
- api-energy-meters
|
||
summary: Patch Energy Meter
|
||
description: "Partially update a meter epoch: rename, edit note, or correct\
|
||
\ started_at.\n\n- ``label``: updates the human-readable label.\n- ``note``:\
|
||
\ updates the free-form note.\n- ``started_at``: **retroactive correction**\
|
||
\ — shifts this meter's start\n boundary. The service layer maintains timeline\
|
||
\ continuity by also\n updating the preceding meter's ``ended_at``. Validation:\n\
|
||
\ * Must be strictly after the previous meter's own ``started_at``.\n \
|
||
\ * Must be strictly before this meter's ``ended_at`` (if closed).\n Violation\
|
||
\ → 422.\n\n**Retroactive recompute when ``started_at`` changes**: billing\
|
||
\ records in\nthe window ``[min(old, new), now)`` are re-judged to reflect\
|
||
\ the corrected\nmeter attribution.\n\nNot found → 404."
|
||
operationId: patch_energy_meter_api_energy_meters__meter_id__patch
|
||
parameters:
|
||
- name: meter_id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
title: Meter Id
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MeterPatchRequest'
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MeterResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/expose:
|
||
get:
|
||
tags:
|
||
- api-expose
|
||
summary: Get Expose
|
||
description: 'Return the full exposable-entity catalog with toggle states and
|
||
MQTT status.
|
||
|
||
|
||
The catalog is computed dynamically from registered providers (e.g. the
|
||
|
||
Modbus provider enumerates all enabled devices and their metric entities).
|
||
|
||
Toggle states come from the ``exposed_entity_toggle`` table; entities with
|
||
|
||
no row default to ``enabled=False``.'
|
||
operationId: get_expose_api_expose_get
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ExposeResponse'
|
||
put:
|
||
tags:
|
||
- api-expose
|
||
summary: Put Expose
|
||
description: 'Set per-entity toggle state.
|
||
|
||
|
||
Accepts a map of ``{key: bool}`` and upserts rows in the
|
||
|
||
``exposed_entity_toggle`` table. Only keys present in ``body.toggles``
|
||
|
||
are touched; other entities'' toggles are left unchanged.
|
||
|
||
|
||
After writing the toggles, triggers a HA Discovery re-publish so any
|
||
|
||
changes (enabled ↔ disabled) are reflected in Home Assistant immediately.'
|
||
operationId: put_expose_api_expose_put
|
||
parameters:
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ExposeUpdateRequest'
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ExposeUpdateResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/expose/republish:
|
||
post:
|
||
tags:
|
||
- api-expose
|
||
summary: Post Expose Republish
|
||
description: 'Manually trigger a full HA Discovery re-publish.
|
||
|
||
|
||
Calls ``publish_discovery(session)`` from the HA discovery service (M5-T11).
|
||
|
||
Returns a status indicating whether the publish was attempted (or skipped
|
||
|
||
because MQTT / discovery is not enabled / connected).'
|
||
operationId: post_expose_republish_api_expose_republish_post
|
||
parameters:
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RepublishResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/modbus/profiles:
|
||
get:
|
||
tags:
|
||
- api-modbus
|
||
summary: Get Profiles
|
||
description: 'List all available Modbus YAML profiles (name + description).
|
||
|
||
|
||
Intended for the front-end''s device-creation profile drop-down.'
|
||
operationId: get_profiles_api_modbus_profiles_get
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ModbusProfilesResponse'
|
||
/api/modbus/devices:
|
||
get:
|
||
tags:
|
||
- api-modbus
|
||
summary: List Devices
|
||
description: Return all Modbus devices (no pagination — device counts stay small).
|
||
operationId: list_devices_api_modbus_devices_get
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ModbusDeviceListResponse'
|
||
post:
|
||
tags:
|
||
- api-modbus
|
||
summary: Create Device
|
||
description: 'Create a new Modbus device.
|
||
|
||
|
||
- Validates that the referenced ``profile`` exists; returns 422 if not.
|
||
|
||
- Returns 201 with the created device on success.'
|
||
operationId: create_device_api_modbus_devices_post
|
||
parameters:
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ModbusDeviceCreate'
|
||
responses:
|
||
'201':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ModbusDeviceResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/modbus/devices/{uuid}:
|
||
get:
|
||
tags:
|
||
- api-modbus
|
||
summary: Get Device
|
||
description: Return a single Modbus device by UUID.
|
||
operationId: get_device_api_modbus_devices__uuid__get
|
||
parameters:
|
||
- name: uuid
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
title: Uuid
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ModbusDeviceResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
patch:
|
||
tags:
|
||
- api-modbus
|
||
summary: Patch Device
|
||
description: 'Partially update a Modbus device (including enable/disable).
|
||
|
||
|
||
Only fields explicitly provided in the request body are updated.
|
||
|
||
Providing ``profile`` triggers a profile-existence check (422 if unknown).'
|
||
operationId: patch_device_api_modbus_devices__uuid__patch
|
||
parameters:
|
||
- name: uuid
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
title: Uuid
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ModbusDeviceUpdate'
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ModbusDeviceResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
delete:
|
||
tags:
|
||
- api-modbus
|
||
summary: Delete Device
|
||
description: 'Delete a Modbus device.
|
||
|
||
|
||
**Default behaviour (cascade=false)**:
|
||
|
||
Returns 409 Conflict if the device has any associated readings; use
|
||
|
||
``enabled=false`` to disable it instead.
|
||
|
||
|
||
**Cascade delete (cascade=true)**:
|
||
|
||
Permanently deletes the device together with all its readings and any
|
||
|
||
``ExposedEntityToggle`` rows whose key matches ``modbus.<uuid>.*``.
|
||
|
||
Also makes a best-effort attempt to clear the device''s HA Discovery
|
||
|
||
config topics from MQTT (empty retained payload) before the DB rows
|
||
|
||
are removed. MQTT failures are swallowed — the DB deletion proceeds
|
||
|
||
regardless.
|
||
|
||
Returns HTTP 200 with a ``ModbusDeleteResponse`` JSON body on success.
|
||
|
||
|
||
**Application-layer safety**: the 409 guard uses an explicit SELECT COUNT
|
||
|
||
query to return a friendly message. FK RESTRICT is enforced at runtime
|
||
|
||
(the app sets ``PRAGMA foreign_keys=ON``), so the cascade path deletes
|
||
|
||
readings 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.
|
||
|
||
|
||
If no readings exist yet, returns ``{"found": false, "recorded_at": null,
|
||
|
||
"payload": null}`` (200, not 404) so the front-end can distinguish
|
||
|
||
"device exists but has no data" from "device not found".'
|
||
operationId: get_latest_reading_api_modbus_devices__uuid__latest_get
|
||
parameters:
|
||
- name: uuid
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
title: Uuid
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ModbusLatestResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/modbus/devices/{uuid}/readings:
|
||
get:
|
||
tags:
|
||
- api-modbus
|
||
summary: Get Readings
|
||
description: 'Return time-range readings for a device.
|
||
|
||
|
||
When the window contains more rows than ``limit``, the **most recent** N rows
|
||
|
||
are returned (``ORDER BY recorded_at DESC LIMIT n``), then reversed to
|
||
|
||
ascending order before being sent to the client. This ensures that for long
|
||
|
||
time-range requests (e.g. 6 h / 24 h) the caller always sees the latest data
|
||
|
||
rather than the oldest segment of the window.
|
||
|
||
|
||
When the window has fewer rows than ``limit`` the full window is returned
|
||
in
|
||
|
||
ascending order — behaviour is identical to a plain ascending query.
|
||
|
||
|
||
The response schema is unchanged: items are always ``recorded_at`` ascending.
|
||
|
||
|
||
The query uses the ``(device_id, recorded_at)`` composite index for
|
||
|
||
efficient time-window scans.
|
||
|
||
|
||
Query parameters:
|
||
|
||
- ``start``: inclusive lower bound (ISO8601 datetime)
|
||
|
||
- ``end``: inclusive upper bound (ISO8601 datetime)
|
||
|
||
- ``limit``: max rows to return (default 500, max {_READINGS_LIMIT_MAX})'
|
||
operationId: get_readings_api_modbus_devices__uuid__readings_get
|
||
parameters:
|
||
- name: uuid
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
title: Uuid
|
||
- name: start
|
||
in: query
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
- type: 'null'
|
||
title: Start
|
||
- name: end
|
||
in: query
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
- type: 'null'
|
||
title: End
|
||
- name: limit
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
maximum: 5000
|
||
minimum: 1
|
||
default: 500
|
||
title: Limit
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ModbusReadingsResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/modbus/devices/{uuid}/metrics:
|
||
get:
|
||
tags:
|
||
- api-modbus
|
||
summary: Get Metrics
|
||
description: 'Return the metric catalogue for a device''s profile.
|
||
|
||
|
||
Each entry has ``key``, ``label`` (derived from key if the profile has
|
||
|
||
none — underscores → spaces, title-cased), ``unit``, and ``device_class``.
|
||
|
||
This is the authoritative metadata source for front-end card labels and
|
||
|
||
chart axis labels.'
|
||
operationId: get_metrics_api_modbus_devices__uuid__metrics_get
|
||
parameters:
|
||
- name: uuid
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
title: Uuid
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ModbusMetricsResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/modbus/devices/{uuid}/test:
|
||
post:
|
||
tags:
|
||
- api-modbus
|
||
summary: Test Read
|
||
description: 'Immediately read and decode the device once without persisting
|
||
any data.
|
||
|
||
|
||
Useful for validating gateway connectivity and Modbus address settings
|
||
|
||
before relying on the background polling job.
|
||
|
||
|
||
This is a write-class endpoint (it initiates network I/O on demand) and
|
||
|
||
therefore requires a CSRF token. The response payload is never stored.'
|
||
operationId: test_read_api_modbus_devices__uuid__test_post
|
||
parameters:
|
||
- name: uuid
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
title: Uuid
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ModbusTestReadResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/session:
|
||
get:
|
||
tags:
|
||
- api-session
|
||
summary: Get Session
|
||
description: Return the current session user and CSRF token. Returns 401 if
|
||
not authenticated.
|
||
operationId: get_session_api_session_get
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SessionResponse'
|
||
/api/auth/login:
|
||
post:
|
||
tags:
|
||
- api-session
|
||
summary: Post Login
|
||
description: 'Authenticate with username and password.
|
||
|
||
|
||
On success, sets an HttpOnly session cookie and returns the session user +
|
||
CSRF token.
|
||
|
||
On failure, returns 401 with no cookie set.
|
||
|
||
Repeated failures trigger exponential back-off (429 + Retry-After).
|
||
|
||
No X-CSRF-Token required (unauthenticated endpoint).'
|
||
operationId: post_login_api_auth_login_post
|
||
requestBody:
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/LoginRequest'
|
||
required: true
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/SessionResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/auth/logout:
|
||
post:
|
||
tags:
|
||
- api-session
|
||
summary: Post Logout
|
||
description: 'Revoke the current session and clear the session cookie.
|
||
|
||
Requires authentication and X-CSRF-Token header.
|
||
|
||
Returns 204 No Content.'
|
||
operationId: post_logout_api_auth_logout_post
|
||
parameters:
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema: {}
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/auth/password:
|
||
post:
|
||
tags:
|
||
- api-session
|
||
summary: Post Change Password
|
||
description: 'Change the current user''s password.
|
||
|
||
Requires authentication and X-CSRF-Token header.
|
||
|
||
On AuthPasswordChangeError returns 400 with a generic message.
|
||
|
||
On success, force_password_change becomes False (handled by the service).
|
||
|
||
Returns 204 No Content.'
|
||
operationId: post_change_password_api_auth_password_post
|
||
parameters:
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PasswordChangeRequest'
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema: {}
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/auth/totp/setup:
|
||
post:
|
||
tags:
|
||
- api-session
|
||
summary: Post Totp Setup
|
||
description: 'Generate a new pending TOTP secret, otpauth URI, and one-time
|
||
recovery codes.
|
||
|
||
|
||
The secret is stored in the DB but TOTP is NOT yet enabled (totp_enabled stays
|
||
|
||
False until the user confirms with POST /api/auth/totp/enable).
|
||
|
||
|
||
Recovery codes are returned here as plaintext exactly once; their Argon2 hashes
|
||
|
||
are persisted immediately so enable only needs to flip the enabled flag.
|
||
|
||
|
||
Repeating this call replaces any prior pending secret and regenerates codes.
|
||
|
||
|
||
Requires: session cookie + X-CSRF-Token.'
|
||
operationId: post_totp_setup_api_auth_totp_setup_post
|
||
parameters:
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/TotpSetupResponse'
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/auth/totp/enable:
|
||
post:
|
||
tags:
|
||
- api-session
|
||
summary: Post Totp Enable
|
||
description: 'Enable TOTP by confirming with the current 6-digit code from the
|
||
authenticator app.
|
||
|
||
|
||
Requires a prior call to POST /api/auth/totp/setup (so that a pending secret
|
||
|
||
exists). On success, totp_enabled becomes True.
|
||
|
||
|
||
Returns 400 if the code is wrong or there is no pending secret.
|
||
|
||
Requires: session cookie + X-CSRF-Token.'
|
||
operationId: post_totp_enable_api_auth_totp_enable_post
|
||
parameters:
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/TotpEnableRequest'
|
||
responses:
|
||
'204':
|
||
description: Successful Response
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/auth/totp/disable:
|
||
post:
|
||
tags:
|
||
- api-session
|
||
summary: Post Totp Disable
|
||
description: 'Disable TOTP. The caller must provide exactly one of:
|
||
|
||
- ``password``: the user''s current login password, OR
|
||
|
||
- ``code``: the current 6-digit TOTP code.
|
||
|
||
|
||
On success: totp_enabled=False, totp_secret cleared, all recovery codes deleted.
|
||
|
||
Returns 400 if neither credential matches or neither is provided.
|
||
|
||
Requires: session cookie + X-CSRF-Token.'
|
||
operationId: post_totp_disable_api_auth_totp_disable_post
|
||
parameters:
|
||
- name: X-CSRF-Token
|
||
in: header
|
||
required: false
|
||
schema:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: X-Csrf-Token
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/TotpDisableRequest'
|
||
responses:
|
||
'204':
|
||
description: Successful Response
|
||
'422':
|
||
description: Validation Error
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/HTTPValidationError'
|
||
/api/auth/totp:
|
||
get:
|
||
tags:
|
||
- api-session
|
||
summary: Get Totp Status
|
||
description: 'Return the current TOTP status for the authenticated user.
|
||
|
||
|
||
Response contains only ``{"enabled": bool}``.
|
||
|
||
Secret and recovery codes are NEVER returned here.
|
||
|
||
Requires: session cookie only (no CSRF — read-only).'
|
||
operationId: get_totp_status_api_auth_totp_get
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/TotpStatusResponse'
|
||
/homeassistant/publish:
|
||
post:
|
||
tags:
|
||
- homeassistant
|
||
summary: Publish From Homeassistant
|
||
operationId: publish_from_homeassistant_homeassistant_publish_post
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema: {}
|
||
/location/record:
|
||
post:
|
||
tags:
|
||
- location
|
||
summary: Create Location Record
|
||
operationId: create_location_record_location_record_post
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema: {}
|
||
/poo/record:
|
||
post:
|
||
tags:
|
||
- poo
|
||
summary: Create Poo Record
|
||
operationId: create_poo_record_poo_record_post
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema: {}
|
||
/poo/latest:
|
||
get:
|
||
tags:
|
||
- poo
|
||
summary: Notify Latest Poo
|
||
operationId: notify_latest_poo_poo_latest_get
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema: {}
|
||
/public-ip/check:
|
||
get:
|
||
tags:
|
||
- public-ip
|
||
summary: Run Public Ip Check
|
||
operationId: run_public_ip_check_public_ip_check_get
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PublicIPCheckResponse'
|
||
/ticktick/auth/start:
|
||
get:
|
||
tags:
|
||
- ticktick
|
||
summary: Start Ticktick Auth
|
||
operationId: start_ticktick_auth_ticktick_auth_start_get
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema: {}
|
||
/ticktick/auth/code:
|
||
get:
|
||
tags:
|
||
- ticktick
|
||
summary: Handle Ticktick Auth Code
|
||
operationId: handle_ticktick_auth_code_ticktick_auth_code_get
|
||
responses:
|
||
'200':
|
||
description: Successful Response
|
||
content:
|
||
application/json:
|
||
schema: {}
|
||
components:
|
||
schemas:
|
||
CatalogEntrySchema:
|
||
properties:
|
||
entity:
|
||
$ref: '#/components/schemas/ExposableEntitySchema'
|
||
enabled:
|
||
type: boolean
|
||
title: Enabled
|
||
type: object
|
||
required:
|
||
- entity
|
||
- enabled
|
||
title: CatalogEntrySchema
|
||
description: An entity from the catalog with its current toggle state.
|
||
ConfigField:
|
||
properties:
|
||
env_name:
|
||
type: string
|
||
title: Env Name
|
||
label:
|
||
type: string
|
||
title: Label
|
||
value:
|
||
type: string
|
||
title: Value
|
||
secret:
|
||
type: boolean
|
||
title: Secret
|
||
input_type:
|
||
type: string
|
||
title: Input Type
|
||
configured:
|
||
type: boolean
|
||
title: Configured
|
||
type: object
|
||
required:
|
||
- env_name
|
||
- label
|
||
- value
|
||
- secret
|
||
- input_type
|
||
- configured
|
||
title: ConfigField
|
||
ConfigResponse:
|
||
properties:
|
||
sections:
|
||
items:
|
||
$ref: '#/components/schemas/ConfigSection'
|
||
type: array
|
||
title: Sections
|
||
type: object
|
||
required:
|
||
- sections
|
||
title: ConfigResponse
|
||
ConfigSection:
|
||
properties:
|
||
name:
|
||
type: string
|
||
title: Name
|
||
fields:
|
||
items:
|
||
$ref: '#/components/schemas/ConfigField'
|
||
type: array
|
||
title: Fields
|
||
type: object
|
||
required:
|
||
- name
|
||
- fields
|
||
title: ConfigSection
|
||
ConfigUpdateRequest:
|
||
properties:
|
||
updates:
|
||
additionalProperties:
|
||
type: string
|
||
type: object
|
||
title: Updates
|
||
type: object
|
||
required:
|
||
- updates
|
||
title: ConfigUpdateRequest
|
||
description: Flat mapping of env_name → value, mirroring the existing form semantics.
|
||
ConfigUpdateResponse:
|
||
properties:
|
||
sections:
|
||
items:
|
||
$ref: '#/components/schemas/ConfigSection'
|
||
type: array
|
||
title: Sections
|
||
type: object
|
||
required:
|
||
- sections
|
||
title: ConfigUpdateResponse
|
||
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.
|
||
|
||
|
||
``effective_from`` defaults to the current UTC time if not provided,
|
||
|
||
giving the first version an open-ended start from "now".
|
||
|
||
``kind`` is validated at the application layer against the profile registry;
|
||
|
||
clients 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.
|
||
|
||
|
||
Returned by GET /api/energy/contracts/{id} and by successful POST / PATCH
|
||
|
||
operations 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}.
|
||
|
||
|
||
All fields are optional. Sending ``active=true`` activates this contract
|
||
|
||
(deactivating all others); ``active=false`` deactivates it without affecting
|
||
|
||
other 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).
|
||
|
||
|
||
Used 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.
|
||
|
||
|
||
``found`` is False when no dsmr_reading rows exist yet. The front-end
|
||
|
||
should 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.
|
||
|
||
|
||
``value_getter`` is intentionally excluded — it is a non-serialisable
|
||
|
||
callable and is only used internally by the HA Discovery service.'
|
||
ExposeResponse:
|
||
properties:
|
||
catalog:
|
||
items:
|
||
$ref: '#/components/schemas/CatalogEntrySchema'
|
||
type: array
|
||
title: Catalog
|
||
mqtt_status:
|
||
$ref: '#/components/schemas/MqttStatusSchema'
|
||
type: object
|
||
required:
|
||
- catalog
|
||
- mqtt_status
|
||
title: ExposeResponse
|
||
description: Response for GET /api/expose.
|
||
ExposeUpdateRequest:
|
||
properties:
|
||
toggles:
|
||
additionalProperties:
|
||
type: boolean
|
||
type: object
|
||
title: Toggles
|
||
type: object
|
||
required:
|
||
- toggles
|
||
title: ExposeUpdateRequest
|
||
description: 'Request body for PUT /api/expose.
|
||
|
||
|
||
``toggles`` is a map from entity key to desired enabled state (bool).
|
||
|
||
Only keys present in the map are updated; absent keys are untouched.'
|
||
ExposeUpdateResponse:
|
||
properties:
|
||
catalog:
|
||
items:
|
||
$ref: '#/components/schemas/CatalogEntrySchema'
|
||
type: array
|
||
title: Catalog
|
||
mqtt_status:
|
||
$ref: '#/components/schemas/MqttStatusSchema'
|
||
type: object
|
||
required:
|
||
- catalog
|
||
- mqtt_status
|
||
title: ExposeUpdateResponse
|
||
description: Response for PUT /api/expose (returns updated catalog + status).
|
||
HTTPValidationError:
|
||
properties:
|
||
detail:
|
||
items:
|
||
$ref: '#/components/schemas/ValidationError'
|
||
type: array
|
||
title: Detail
|
||
type: object
|
||
title: HTTPValidationError
|
||
LocationRecord:
|
||
properties:
|
||
person:
|
||
type: string
|
||
title: Person
|
||
datetime:
|
||
type: string
|
||
title: Datetime
|
||
latitude:
|
||
type: number
|
||
title: Latitude
|
||
longitude:
|
||
type: number
|
||
title: Longitude
|
||
altitude:
|
||
anyOf:
|
||
- type: number
|
||
- type: 'null'
|
||
title: Altitude
|
||
type: object
|
||
required:
|
||
- person
|
||
- datetime
|
||
- latitude
|
||
- longitude
|
||
- altitude
|
||
title: LocationRecord
|
||
LocationUpdateRequest:
|
||
properties:
|
||
latitude:
|
||
anyOf:
|
||
- type: number
|
||
- type: 'null'
|
||
title: Latitude
|
||
longitude:
|
||
anyOf:
|
||
- type: number
|
||
- type: 'null'
|
||
title: Longitude
|
||
altitude:
|
||
anyOf:
|
||
- type: number
|
||
- type: 'null'
|
||
title: Altitude
|
||
type: object
|
||
title: LocationUpdateRequest
|
||
description: PATCH body for a location record — all fields optional; PK fields
|
||
excluded.
|
||
LocationsResponse:
|
||
properties:
|
||
items:
|
||
items:
|
||
$ref: '#/components/schemas/LocationRecord'
|
||
type: array
|
||
title: Items
|
||
limit:
|
||
type: integer
|
||
title: Limit
|
||
offset:
|
||
type: integer
|
||
title: Offset
|
||
type: object
|
||
required:
|
||
- items
|
||
- limit
|
||
- offset
|
||
title: LocationsResponse
|
||
LoginRequest:
|
||
properties:
|
||
username:
|
||
type: string
|
||
title: Username
|
||
password:
|
||
type: string
|
||
title: Password
|
||
totp_code:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: Totp Code
|
||
type: object
|
||
required:
|
||
- username
|
||
- password
|
||
title: LoginRequest
|
||
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.
|
||
|
||
|
||
Prices are the *effective* buy prices as used by the billing engine
|
||
|
||
(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.
|
||
|
||
|
||
Declares a new meter epoch (swap, home move, or initial declaration). The
|
||
|
||
service layer closes the current active meter for the given commodity at
|
||
|
||
``started_at`` and opens a new one.
|
||
|
||
|
||
``started_at`` follows the Principle-A localisation convention: a
|
||
|
||
timezone-naive value is interpreted as the **server''s local wall-clock time**
|
||
|
||
(e.g. CEST midnight → stored as UTC the night before); a timezone-aware
|
||
|
||
value is converted to UTC as-is. Omitting ``started_at`` is not allowed —
|
||
|
||
every meter declaration must carry an explicit start timestamp.
|
||
|
||
|
||
``commodity`` defaults to ``"electricity"``; the field is available for
|
||
|
||
future 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.
|
||
|
||
|
||
Meters are returned in ascending ``started_at`` order so the caller sees
|
||
|
||
the 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}.
|
||
|
||
|
||
All fields are optional. Only non-``None`` values are applied.
|
||
|
||
|
||
Updating ``started_at`` is a **retroactive correction**: the service layer
|
||
|
||
maintains timeline continuity (adjusting the preceding meter''s ``ended_at``)
|
||
|
||
and the API layer triggers ``recompute_range`` over the affected window so
|
||
|
||
that 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.
|
||
|
||
|
||
``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.
|
||
|
||
|
||
``found`` is False and ``recorded_at``/``payload`` are None when the device
|
||
|
||
has no readings yet. Callers should check ``found`` before using the values.'
|
||
ModbusMetricsResponse:
|
||
properties:
|
||
profile:
|
||
type: string
|
||
title: Profile
|
||
metrics:
|
||
items:
|
||
$ref: '#/components/schemas/MetricInfo'
|
||
type: array
|
||
title: Metrics
|
||
type: object
|
||
required:
|
||
- profile
|
||
- metrics
|
||
title: ModbusMetricsResponse
|
||
description: Response schema for GET /api/modbus/devices/{uuid}/metrics.
|
||
ModbusProfilesResponse:
|
||
properties:
|
||
profiles:
|
||
items:
|
||
$ref: '#/components/schemas/ProfileSummary'
|
||
type: array
|
||
title: Profiles
|
||
type: object
|
||
required:
|
||
- profiles
|
||
title: ModbusProfilesResponse
|
||
description: Response schema for GET /api/modbus/profiles.
|
||
ModbusReadingResponse:
|
||
properties:
|
||
recorded_at:
|
||
type: string
|
||
format: date-time
|
||
title: Recorded At
|
||
payload:
|
||
additionalProperties: true
|
||
type: object
|
||
title: Payload
|
||
type: object
|
||
required:
|
||
- recorded_at
|
||
- payload
|
||
title: ModbusReadingResponse
|
||
description: 'A single reading row: timestamp + decoded payload.'
|
||
ModbusReadingsResponse:
|
||
properties:
|
||
items:
|
||
items:
|
||
$ref: '#/components/schemas/ModbusReadingResponse'
|
||
type: array
|
||
title: Items
|
||
type: object
|
||
required:
|
||
- items
|
||
title: ModbusReadingsResponse
|
||
description: Response schema for the readings time-range endpoint.
|
||
ModbusTestReadResponse:
|
||
properties:
|
||
ok:
|
||
type: boolean
|
||
title: Ok
|
||
payload:
|
||
anyOf:
|
||
- additionalProperties: true
|
||
type: object
|
||
- type: 'null'
|
||
title: Payload
|
||
error:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: Error
|
||
type: object
|
||
required:
|
||
- ok
|
||
title: ModbusTestReadResponse
|
||
description: 'Response for POST /api/modbus/devices/{uuid}/test.
|
||
|
||
|
||
On success ``ok=True`` and ``payload`` contains the decoded values.
|
||
|
||
On failure ``ok=False`` and ``error`` describes the problem.'
|
||
MqttStatusSchema:
|
||
properties:
|
||
mqtt_configured:
|
||
type: boolean
|
||
title: Mqtt Configured
|
||
mqtt_connected:
|
||
type: boolean
|
||
title: Mqtt Connected
|
||
discovery_enabled:
|
||
type: boolean
|
||
title: Discovery Enabled
|
||
type: object
|
||
required:
|
||
- mqtt_configured
|
||
- mqtt_connected
|
||
- discovery_enabled
|
||
title: MqttStatusSchema
|
||
description: Connection status for MQTT and HA Discovery.
|
||
MqttTestResponse:
|
||
properties:
|
||
result:
|
||
type: string
|
||
enum:
|
||
- success
|
||
- config-error
|
||
- failed
|
||
title: Result
|
||
message:
|
||
type: string
|
||
title: Message
|
||
type: object
|
||
required:
|
||
- result
|
||
- message
|
||
title: MqttTestResponse
|
||
description: Response from POST /api/config/mqtt/test.
|
||
PasswordChangeRequest:
|
||
properties:
|
||
current_password:
|
||
type: string
|
||
title: Current Password
|
||
new_password:
|
||
type: string
|
||
title: New Password
|
||
confirm_password:
|
||
type: string
|
||
title: Confirm Password
|
||
type: object
|
||
required:
|
||
- current_password
|
||
- new_password
|
||
- confirm_password
|
||
title: PasswordChangeRequest
|
||
PooRecord:
|
||
properties:
|
||
timestamp:
|
||
type: string
|
||
title: Timestamp
|
||
status:
|
||
type: string
|
||
title: Status
|
||
latitude:
|
||
type: number
|
||
title: Latitude
|
||
longitude:
|
||
type: number
|
||
title: Longitude
|
||
type: object
|
||
required:
|
||
- timestamp
|
||
- status
|
||
- latitude
|
||
- longitude
|
||
title: PooRecord
|
||
PooResponse:
|
||
properties:
|
||
items:
|
||
items:
|
||
$ref: '#/components/schemas/PooRecord'
|
||
type: array
|
||
title: Items
|
||
limit:
|
||
type: integer
|
||
title: Limit
|
||
offset:
|
||
type: integer
|
||
title: Offset
|
||
type: object
|
||
required:
|
||
- items
|
||
- limit
|
||
- offset
|
||
title: PooResponse
|
||
PooUpdateRequest:
|
||
properties:
|
||
status:
|
||
anyOf:
|
||
- type: string
|
||
- type: 'null'
|
||
title: Status
|
||
latitude:
|
||
anyOf:
|
||
- type: number
|
||
- type: 'null'
|
||
title: Latitude
|
||
longitude:
|
||
anyOf:
|
||
- type: number
|
||
- type: 'null'
|
||
title: Longitude
|
||
type: object
|
||
title: PooUpdateRequest
|
||
description: PATCH body for a poo record — all fields optional; PK field excluded.
|
||
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.
|
||
|
||
|
||
``kind`` mirrors the active contract kind:
|
||
|
||
- ``"tibber"`` → ``points`` has actual 15-min price entries; ``tariff`` is
|
||
null.
|
||
|
||
- ``"manual"`` → ``points`` is empty; ``tariff`` carries the fixed-rate table.
|
||
|
||
- ``None`` → no active contract; both ``points`` and ``tariff`` are empty/null.
|
||
|
||
|
||
``currency`` comes from the active contract (or "EUR" fallback).
|
||
|
||
``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.
|
||
|
||
|
||
``profiles`` is a list of raw profile dicts as produced by
|
||
|
||
``list_profiles()`` (Pydantic model dumps). Each entry contains at minimum
|
||
|
||
``kind`` and ``label``; the full nested structure allows the front-end to
|
||
|
||
render 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.
|
||
|
||
|
||
All monetary values are in ``currency``.
|
||
|
||
|
||
``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.
|
||
|
||
|
||
Carries enough fields for the front-end to confirm the API is working and
|
||
|
||
display 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.
|
||
|
||
|
||
Possible ``result`` values:
|
||
|
||
|
||
- ``"success"`` — Tibber API responded with a valid price.
|
||
|
||
- ``"config-error"`` — Token is missing or not configured.
|
||
|
||
- ``"failed"`` — API call failed (auth rejected, network error, timeout,
|
||
etc.).
|
||
|
||
|
||
``price`` is populated only on ``"success"``; it is null otherwise.
|
||
|
||
``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.
|
||
|
||
|
||
Exactly one of ``password`` or ``code`` must be provided.'
|
||
TotpEnableRequest:
|
||
properties:
|
||
code:
|
||
type: string
|
||
title: Code
|
||
type: object
|
||
required:
|
||
- code
|
||
title: TotpEnableRequest
|
||
description: The user confirms setup by providing the 6-digit TOTP code.
|
||
TotpSetupResponse:
|
||
properties:
|
||
secret:
|
||
type: string
|
||
title: Secret
|
||
otpauth_uri:
|
||
type: string
|
||
title: Otpauth Uri
|
||
recovery_codes:
|
||
items:
|
||
type: string
|
||
type: array
|
||
title: Recovery Codes
|
||
type: object
|
||
required:
|
||
- secret
|
||
- otpauth_uri
|
||
- recovery_codes
|
||
title: TotpSetupResponse
|
||
description: 'Returned once after a setup call.
|
||
|
||
|
||
``secret`` and ``recovery_codes`` are **one-time plaintext values**.
|
||
|
||
They are never returned again by any subsequent API call.
|
||
|
||
The frontend must display and instruct the user to save them before confirming.'
|
||
TotpStatusResponse:
|
||
properties:
|
||
enabled:
|
||
type: boolean
|
||
title: Enabled
|
||
type: object
|
||
required:
|
||
- enabled
|
||
title: TotpStatusResponse
|
||
description: Minimal status response — never exposes secret or recovery codes.
|
||
ValidationError:
|
||
properties:
|
||
loc:
|
||
items:
|
||
anyOf:
|
||
- type: string
|
||
- type: integer
|
||
type: array
|
||
title: Location
|
||
msg:
|
||
type: string
|
||
title: Message
|
||
type:
|
||
type: string
|
||
title: Error Type
|
||
type: object
|
||
required:
|
||
- loc
|
||
- msg
|
||
- type
|
||
title: ValidationError
|
||
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.
|