Files
home-automation/openapi/openapi.yaml
T

3893 lines
106 KiB
YAML
Raw Permalink Normal View History

2026-04-19 23:25:13 +02:00
openapi: 3.1.0
info:
title: Home Automation Backend (Python)
2026-04-20 20:40:04 +02:00
description: Home automation backend with auth, runtime config, Home Assistant integrations,
TickTick integration, and SQLite-backed recorders.
2026-04-19 23:25:13 +02:00
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'
2026-06-12 23:24:17 +02:00
/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'
2026-04-20 10:42:35 +02:00
/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: {}
2026-04-19 23:25:13 +02:00
/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'
2026-04-20 20:40:04 +02:00
/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: {}
2026-04-19 23:25:13 +02:00
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).
2026-04-20 15:16:47 +02:00
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
type: array
title: Detail
type: object
title: HTTPValidationError
2026-06-12 23:24:17 +02:00
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.
2026-06-12 23:24:17 +02:00
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
2026-06-12 23:24:17 +02:00
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
2026-06-12 23:24:17 +02:00
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.
2026-04-19 23:25:13 +02:00
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.
2026-04-20 15:16:47 +02:00
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.