REST API reference¶
Generated from the OpenAPI schema of franklinwh-modbus-bridge 0.2.11: 178 operations in 19 groups. The raw schema is openapi.json; a running bridge serves the same at /openapi.json, with interactive docs at /docs and /redoc.
Access is what the route itself enforces (see Authentication). Under Home Assistant ingress every request is the ingress admin. A route behind a feature module answers 403 while that module is off.
Most routes return plain JSON objects without a declared response model, so the schema lists their response as untyped. Request bodies are typed.
Health and status¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/health |
Health | open |
GET |
/api/health/connectivity |
Connectivity | open |
GET |
/api/status |
Status | open |
GET /api/health¶
Access: open.
GET /api/health/connectivity¶
Outage-aware connectivity: per-gateway connected/last-good-poll state plus recent outages. Powers the scheduler's outage view (WiFi→4G drops etc.).
Access: open.
GET /api/status¶
Access: open.
Authentication and sessions¶
| Method | Path | Summary | Access |
|---|---|---|---|
POST |
/api/auth/login |
Login | open |
POST |
/api/auth/logout |
Logout | open |
GET |
/api/auth/session-status |
Session Status | open |
POST |
/api/auth/password |
Change Own Password | open |
GET |
/api/auth/me |
Me | open |
GET |
/api/auth/sessions |
List Sessions | admin role |
POST |
/api/auth/sessions/{sid}/revoke |
Revoke Session | admin role |
GET |
/api/auth/events |
List Auth Events | admin role |
POST /api/auth/login¶
Verify credentials, set the session cookie. Accepts form or JSON.
Access: open.
POST /api/auth/logout¶
Access: open.
GET /api/auth/session-status¶
Why a login ended. No auth: it's asked by a page that just got a 401.
sid is the login id the page saved at login time; it is a random
secret only that browser holds. If the record says the login is still
open but the browser has stopped presenting it, it is ended here with the
reason that fits (idle past the cookie lifetime = expired, else the
browser dropped it), so the audit trail has it too.
Access: open.
| Parameter | In | Type | Required |
|---|---|---|---|
sid |
query | string | no |
POST /api/auth/password¶
Change your own password. Your other logins end; this one stays.
Access: open.
Request body (application/json): PasswordChange
| Field | Type | Required | Default |
|---|---|---|---|
current_password |
string | yes | |
new_password |
string | yes |
GET /api/auth/me¶
Access: open.
GET /api/auth/sessions¶
Logins, most recently active first. active=true = still open.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
active |
query | boolean | no |
limit |
query | integer | no |
POST /api/auth/sessions/{sid}/revoke¶
End one login now; its page shows "An administrator ended this login".
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
sid |
path | string | yes |
GET /api/auth/events¶
Audit trail: logins, failed logins, logouts, logins ended (newest first).
username (as typed at sign-in, any case) and event narrow it.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
limit |
query | integer | no |
username |
query | string | no |
event |
query | string | no |
Admin: points, metrics, logs, sequences, backup¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/config/{section} |
Get Config | any signed-in role |
PUT |
/api/config/{key} |
Set Config | admin role |
GET |
/api/gateway |
Get Gateway | any signed-in role |
GET |
/api/gateway/test |
Test Gateway | any signed-in role |
GET |
/api/models |
Get Models | any signed-in role |
GET |
/api/catalog/points |
List Catalog Points | any signed-in role |
GET |
/api/models/export |
Export Models | any signed-in role |
POST |
/api/models/{model_id}/read |
Read Model | admin role |
POST |
/api/models/refresh |
Refresh Models | admin role |
GET |
/api/points |
Get Points | any signed-in role |
GET |
/api/points/{point_id} |
Get Point | any signed-in role |
GET |
/api/metrics |
Get Metrics | any signed-in role |
GET |
/api/stats/storage |
Get Storage Info | any signed-in role |
POST |
/api/metrics/archive |
Run Archive | admin role |
GET |
/api/alarm-events |
Get Alarm Events | any signed-in role |
GET |
/api/events |
Get Events | any signed-in role |
GET |
/api/settings/metrics |
Get Metrics Settings | any signed-in role |
PUT |
/api/settings/metrics |
Put Metrics Settings | admin role |
GET |
/api/stats |
Get Stats | any signed-in role |
GET |
/api/logs |
Get Logs | any signed-in role |
POST |
/api/sequence/execute |
Execute Sequence | control capability |
GET |
/api/sequences |
List Sequences | any signed-in role |
GET |
/api/sequences/{name} |
Get Sequence | any signed-in role |
PUT |
/api/sequences/{name} |
Save Sequence | admin role |
DELETE |
/api/sequences/{name} |
Delete Sequence | admin role |
POST |
/api/sequences/{name}/rename |
Rename Sequence | admin role |
GET |
/api/pics |
Get Pics | any signed-in role |
PUT |
/api/pics |
Put Pics | admin role |
GET |
/api/battery/limits |
Get Battery Limits | any signed-in role |
POST |
/api/backup/create |
Create Backup | admin role |
GET |
/api/backup/list |
List Backups | any signed-in role |
POST |
/api/backup/restore |
Restore Backup | admin role |
GET |
/api/backup/download/{name} |
Download Backup | any signed-in role |
GET |
/api/metrics/export |
Export Metrics Endpoint | any signed-in role |
POST |
/api/metrics/import |
Import Metrics Endpoint | admin role |
GET /api/config/{section}¶
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
section |
path | string | yes |
PUT /api/config/{key}¶
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
key |
path | string | yes |
Request body (application/json): ConfigUpdate
| Field | Type | Required | Default |
|---|---|---|---|
value |
object | yes |
GET /api/gateway¶
The address of one gateway (the primary one unless gateway_id).
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
gateway_id |
query | string | no |
GET /api/gateway/test¶
Test TCP connectivity to a gateway's configured host:port.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
gateway_id |
query | string | no |
GET /api/models¶
The SunSpec catalog captured for a gateway (the primary one unless named).
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
gateway_id |
query | string | no |
GET /api/catalog/points¶
Flat list of catalog points with metadata + whether each is published (promoted into an enabled group). Powers the Explorer → group picker.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
gateway_id |
query | string | no |
GET /api/models/export¶
Export a gateway's SunSpec models with their current values (#228).
models: comma-separated model ids; empty for every model.
format: json or csv. redact_serial (default true) replaces
the serial number, in model 1's SN and anywhere else it appears, with
<serial>. The gateway's address and name are never included.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
gateway_id |
query | string | no |
models |
query | string | no |
format |
query | string | no |
redact_serial |
query | boolean | no |
POST /api/models/{model_id}/read¶
On-demand read of a single SunSpec model from the device.
Connects to the controller, calls model.read() to fetch live register values, then returns the point name→value map. Also injects the values into the sample bus so the Explorer sees them immediately.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
model_id |
path | integer | yes |
gateway_id |
query | string | no |
POST /api/models/refresh¶
Re-read a gateway's SunSpec model chain into its catalog.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
gateway_id |
query | string | no |
GET /api/points¶
Access: any signed-in role.
GET /api/points/{point_id}¶
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
point_id |
path | string | yes |
GET /api/metrics¶
Return power time-series for the dashboard chart.
Supports two modes:
- Relative range: ?range=30m (default) — last N from now.
- Absolute date range: ?start=<epoch>&end=<epoch> — historical query.
Optional ?bucket=5m downsamples to the given interval.
Valid buckets: 1m, 5m, 10m, 15m, 30m, 1h.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
range |
query | string or null | no |
start |
query | number or null | no |
end |
query | number or null | no |
bucket |
query | string or null | no |
gateway_id |
query | string or null | no |
GET /api/stats/storage¶
Return DB storage statistics (row counts, sizes, time ranges).
Access: any signed-in role.
POST /api/metrics/archive¶
Manually trigger metrics archival (downsample old raw data).
Access: admin role.
GET /api/alarm-events¶
Return alarm events in a time window.
Provide either start+end (Unix timestamps) or range
(e.g. 30m, 1h, 24h). Events are sparse — only rows
written on register state changes are returned.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
start |
query | number or null | no |
end |
query | number or null | no |
range |
query | string or null | no |
gateway_id |
query | string or null | no |
GET /api/events¶
Return durationed alarm + PV-Clipping events in a time window.
Unlike /alarm-events (raw SET/CLR transitions), this pairs each
transition into a single row with a start time, end time, and
duration, and merges in PV-Clipping/Grid-Forming intervals derived
from metrics.grid_mode. Same start+end / range params
as /alarm-events.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
start |
query | number or null | no |
end |
query | number or null | no |
range |
query | string or null | no |
gateway_id |
query | string or null | no |
GET /api/settings/metrics¶
Read metrics retention + raw-resolution config.
Access: any signed-in role.
PUT /api/settings/metrics¶
Update metrics retention and/or the raw full-resolution window.
raw_age_days is how long full-resolution samples are kept before being downsampled to 5-min buckets; it must be >= 1 and <= retention_days (keeping raw longer than total retention is meaningless).
Access: admin role.
Request body (application/json): MetricsSettingsUpdate
| Field | Type | Required | Default |
|---|---|---|---|
retention_days |
integer or null | no | |
raw_age_days |
integer or null | no |
GET /api/stats¶
Return operational statistics (uptime, polls, errors, data quality).
Access: any signed-in role.
GET /api/logs¶
Persisted log history, filterable by time span (start_ts/end_ts) + level/ gateway/source/search. Returned oldest-first (the UI reverses for display). Falls back to the in-memory buffer on a fresh DB (default, no-range view).
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
limit |
query | integer | no |
start_ts |
query | number or null | no |
end_ts |
query | number or null | no |
level |
query | string or null | no |
gateway |
query | string or null | no |
source |
query | string or null | no |
search |
query | string or null | no |
POST /api/sequence/execute¶
Execute a SunSpec Modbus sequence against one or all gateways.
It writes real registers (WSetEna/WSetPct included, i.e. a battery
dispatch), so it needs the control capability, and every run is
recorded in the control log with who ran it and what it wrote.
Access: control capability.
Request body (application/json): SequenceExecRequest
| Field | Type | Required | Default |
|---|---|---|---|
sequence |
list of object or null | no | |
inline |
string or null | no | |
dry_run |
boolean | no | false |
gateway_id |
string or null | no |
GET /api/sequences¶
List saved sequence files.
Access: any signed-in role.
GET /api/sequences/{name}¶
Get a saved sequence file's content.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
name |
path | string | yes |
PUT /api/sequences/{name}¶
Save or update a sequence file.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
name |
path | string | yes |
Request body (application/json): SequenceSaveRequest
| Field | Type | Required | Default |
|---|---|---|---|
content |
string | yes |
DELETE /api/sequences/{name}¶
Delete a sequence file.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
name |
path | string | yes |
POST /api/sequences/{name}/rename¶
Rename a sequence file.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
name |
path | string | yes |
Request body (application/json): SequenceRenameRequest
| Field | Type | Required | Default |
|---|---|---|---|
new_name |
string | yes |
GET /api/pics¶
Return all PICS compliance statuses.
Access: any signed-in role.
PUT /api/pics¶
Upsert a PICS compliance status for a model point.
Access: admin role.
Request body (application/json): PicsUpdateRequest
| Field | Type | Required | Default |
|---|---|---|---|
model_id |
integer | yes | |
point_name |
string | yes | |
status |
string | yes | |
notes |
string or null | no |
GET /api/battery/limits¶
Battery power limits a force command may use: trusted ratings only (#133).
Access: any signed-in role.
POST /api/backup/create¶
Create a new database backup.
Access: admin role.
Request body (application/json): BackupCreateRequest or null
GET /api/backup/list¶
List available backups, newest first.
Access: any signed-in role.
POST /api/backup/restore¶
Restore the database from a named backup.
Creates a pre-restore snapshot automatically. The bridge should be restarted after restore to reload state.
Access: admin role.
Request body (application/json): BackupRestoreRequest
| Field | Type | Required | Default |
|---|---|---|---|
name |
string | yes |
GET /api/backup/download/{name}¶
Download a backup archive as a ZIP file.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
name |
path | string | yes |
GET /api/metrics/export¶
Export metrics as CSV or JSON for the given time range.
Unlike the chart endpoint, this returns every stored row (no downsampling) so exports are lossless.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
range |
query | string | no |
format |
query | string | no |
POST /api/metrics/import¶
Import metrics from a CSV file.
Expects columns: timestamp, battery_w, grid_w, solar_w, home_w, soc. Timestamp can be ISO-8601 or Unix epoch float.
Access: admin role.
Request body (multipart/form-data): Body_import_metrics_endpoint_api_metrics_import_post
| Field | Type | Required | Default |
|---|---|---|---|
file |
string | yes |
MQTT publishing¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/mqtt/config |
Get Config | any signed-in role |
PATCH |
/api/mqtt/config |
Update Config | admin role |
GET |
/api/mqtt/namespace |
Get Namespace | any signed-in role |
POST |
/api/mqtt/namespace |
Post Namespace | admin role |
GET |
/api/mqtt/status |
Get Status | any signed-in role |
POST |
/api/mqtt/reconnect |
Reconnect | admin role |
POST |
/api/mqtt/publish |
Republish Discovery | admin role |
POST |
/api/mqtt/unpublish |
Unpublish Discovery | admin role |
GET |
/api/mqtt/detect-broker |
Detect Broker | any signed-in role |
POST |
/api/mqtt/test |
Test Connection | admin role |
GET |
/api/mqtt/topics |
Get Topics | any signed-in role |
GET /api/mqtt/config¶
The stored config, plus where each broker field in use comes from (#103).
sources maps host/port/username/password to environment /
settings / supervisor / default; env_vars names the variable
for each field the environment sets, so the form can say why it's locked.
Access: any signed-in role.
PATCH /api/mqtt/config¶
Access: admin role.
Request body (application/json): MqttConfigUpdate
| Field | Type | Required | Default |
|---|---|---|---|
host |
string or null | no | |
port |
integer or null | no | |
username |
string or null | no | |
password |
string or null | no | |
tls_mode |
string or null | no | |
enabled |
boolean or null | no | |
client_id |
string or null | no | |
qos |
integer or null | no | |
retain_discovery |
boolean or null | no | |
topic_prefix |
string or null | no | |
discovery_prefix |
string or null | no |
GET /api/mqtt/namespace¶
The namespace in use, the choices, and whether a change needs confirming.
Access: any signed-in role.
POST /api/mqtt/namespace¶
Change this bridge's MQTT namespace (#87). Admin-only (router gate).
Before anything is published it simply switches. After, confirm is
required: the old namespace's retained discovery configs that THIS bridge
wrote are removed (another producer's are left), then everything is
republished under the new one. The answer carries the counts.
Access: admin role.
Request body (application/json): NamespaceChange
| Field | Type | Required | Default |
|---|---|---|---|
topic_prefix |
string | yes | |
confirm |
boolean | no | false |
GET /api/mqtt/status¶
Access: any signed-in role.
POST /api/mqtt/reconnect¶
Access: admin role.
POST /api/mqtt/publish¶
Access: admin role.
POST /api/mqtt/unpublish¶
Access: admin role.
GET /api/mqtt/detect-broker¶
Access: any signed-in role.
POST /api/mqtt/test¶
Test TCP connectivity. Accepts optional {host, port} in the body to test before saving; falls back to the stored config.
Access: admin role.
GET /api/mqtt/topics¶
Access: any signed-in role.
Publishing groups¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/groups |
List Groups | any signed-in role |
POST |
/api/groups |
Create Group | admin role |
GET |
/api/groups/entities |
List Entities | any signed-in role |
GET |
/api/groups/disabled |
List Disabled | any signed-in role |
GET |
/api/groups/{slug} |
Get Group | any signed-in role |
PATCH |
/api/groups/{slug} |
Patch Group | admin role |
DELETE |
/api/groups/{slug} |
Delete Group | admin role |
GET |
/api/groups/{slug}/members |
List Members | any signed-in role |
PUT |
/api/groups/{slug}/members |
Replace Members | admin role |
POST |
/api/groups/{slug}/members |
Add Member | admin role |
DELETE |
/api/groups/{slug}/members/{entity_slug} |
Delete Member | admin role |
GET |
/api/groups/{slug}/points |
List Group Points | any signed-in role |
POST |
/api/groups/{slug}/points |
Add Group Point | admin role |
DELETE |
/api/groups/{slug}/points |
Remove Group Point | admin role |
GET /api/groups¶
List all publishing groups with member counts.
Access: any signed-in role.
POST /api/groups¶
Create a new custom publishing group.
Access: admin role.
Request body (application/json): GroupCreate
| Field | Type | Required | Default |
|---|---|---|---|
slug |
string | yes | |
name |
string | yes | |
description |
string | no | |
enabled |
boolean | no | true |
members |
list of string | no |
GET /api/groups/entities¶
Return the full entity catalog — all available entity definitions.
Used by the Settings UI to populate group member pickers. Each entry includes slug, name, ha_type, state_group, and whether it's a control entity.
Access: any signed-in role.
GET /api/groups/disabled¶
Return slugs of entities disabled by group settings.
Access: any signed-in role.
GET /api/groups/{slug}¶
Get a single group with its member list.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
slug |
path | string | yes |
PATCH /api/groups/{slug}¶
Update name, description, or enabled state of a group.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
slug |
path | string | yes |
Request body (application/json): GroupUpdate
| Field | Type | Required | Default |
|---|---|---|---|
name |
string or null | no | |
description |
string or null | no | |
enabled |
boolean or null | no |
DELETE /api/groups/{slug}¶
Delete a custom group. Default groups cannot be deleted.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
slug |
path | string | yes |
GET /api/groups/{slug}/members¶
List entity slugs in a group.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
slug |
path | string | yes |
PUT /api/groups/{slug}/members¶
Replace all members of a group.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
slug |
path | string | yes |
Request body (application/json): MemberUpdate
| Field | Type | Required | Default |
|---|---|---|---|
members |
list of string | yes |
POST /api/groups/{slug}/members¶
Add an entity to a group.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
slug |
path | string | yes |
Request body (application/json): SingleMember
| Field | Type | Required | Default |
|---|---|---|---|
entity_slug |
string | yes |
DELETE /api/groups/{slug}/members/{entity_slug}¶
Remove an entity from a group.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
slug |
path | string | yes |
entity_slug |
path | string | yes |
GET /api/groups/{slug}/points¶
List the catalog points promoted into a group.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
slug |
path | string | yes |
POST /api/groups/{slug}/points¶
Promote a catalog point ("model.point") into a group.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
slug |
path | string | yes |
Request body (application/json): PointMember
| Field | Type | Required | Default |
|---|---|---|---|
ref |
string | yes | |
disp_name |
string or null | no | |
disp_unit |
string or null | no |
DELETE /api/groups/{slug}/points¶
Remove a promoted point from a group (ref as ?ref=model.point).
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
slug |
path | string | yes |
ref |
query | string | yes |
Site, services and gateways¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/site |
Get Site | any signed-in role |
PATCH |
/api/site |
Patch Site | admin role |
GET |
/api/site/status |
Get Site Status | any signed-in role |
GET |
/api/system/timezone |
Get Timezone | any signed-in role |
POST |
/api/system/timezone/confirm |
Confirm Timezone Endpoint | admin role |
POST |
/api/services/{service_id}/switch-plan |
Switch Plan | admin role |
GET |
/api/services/{service_id}/export |
Export Service | any signed-in role |
POST |
/api/services/import |
Import Service | admin role |
POST |
/api/tariff/validate-rates |
Validate Rates | admin role |
GET |
/api/services/{service_id}/history-count |
Service History Count | any signed-in role |
GET |
/api/services |
List Services | any signed-in role |
POST |
/api/services |
Add Service | admin role |
PATCH |
/api/services/{service_id} |
Patch Service | admin role |
DELETE |
/api/services/{service_id} |
Remove Service | admin role |
GET |
/api/gateways |
List Gateways | any signed-in role |
POST |
/api/gateways |
Add Gateway | admin role |
GET |
/api/gateways/{gw_id} |
Get Single Gateway | any signed-in role |
PATCH |
/api/gateways/{gw_id} |
Patch Gateway | admin role |
DELETE |
/api/gateways/{gw_id} |
Remove Gateway | admin role |
POST |
/api/gateways/{gw_id}/start |
Start Gateway Endpoint | admin role |
POST |
/api/gateways/{gw_id}/stop |
Stop Gateway Endpoint | admin role |
POST |
/api/gateways/{gw_id}/restart |
Restart Gateway Endpoint | admin role |
POST |
/api/gateways/{gw_id}/healthcheck |
Force Healthcheck Endpoint | admin role |
GET |
/api/gateways/{gw_id}/detect-phases |
Detect Gateway Phases | any signed-in role |
POST |
/api/gateways/{gw_id}/test |
Test Gateway Tcp | admin role |
POST |
/api/gateways/{gw_id}/diagnose |
Diagnose Gateway | admin role |
GET |
/api/gateways/{gw_id}/points |
Get Gateway Points | any signed-in role |
POST |
/api/gateways/{gw_id}/command |
Send Gateway Command | control capability |
GET |
/api/gateways/{gw_id}/battery/limits |
Get Gateway Battery Limits | any signed-in role |
GET |
/api/gateways/{gw_id}/battery-rating |
Get Battery Rating | any signed-in role |
POST |
/api/gateways/{gw_id}/battery-rating/calibration |
Calibrate Battery Rating | control capability |
GET |
/api/gateways/{gw_id}/dispatch-outcomes |
List Dispatch Outcomes | any signed-in role |
GET |
/api/gateways/{gw_id}/link-stats |
Get Link Stats | any signed-in role |
GET /api/site¶
Return site configuration.
Access: any signed-in role.
PATCH /api/site¶
Update site configuration.
Access: admin role.
Request body (application/json): SiteConfigUpdate
| Field | Type | Required | Default |
|---|---|---|---|
name |
string or null | no | |
description |
string or null | no | |
meter_number |
string or null | no | |
account_number |
string or null | no | |
ac_service_type |
integer or null | no | |
aggregate_entities |
boolean or null | no | |
full_backup |
boolean or null | no | |
grid_forming |
boolean or null | no | |
generator_input |
boolean or null | no | |
solar_type |
string or null | no | |
solar_kwp |
number or null | no | |
load_shedding |
boolean or null | no | |
nonbackup_loads |
boolean or null | no | |
battery_label |
string or null | no |
GET /api/site/status¶
Return aggregated site-level status from all gateways.
Access: any signed-in role.
GET /api/system/timezone¶
Detected timezone, whether a user ever confirmed it, and the local time right now — so the prompt can show what the bridge believes rather than asking the user to take it on trust.
Access: any signed-in role.
POST /api/system/timezone/confirm¶
Record the user's decision, optionally correcting the timezone.
Correcting it here only changes what the bridge EXPECTS. The clock itself comes from the container, so a correction must also be applied there (TZ in docker-compose, or the Supervisor's setting) — otherwise the next start reports a mismatch, which is the honest outcome rather than a silent disagreement.
Access: admin role.
Request body (application/json): TimezoneConfirm
| Field | Type | Required | Default |
|---|---|---|---|
timezone |
string or null | no |
POST /api/services/{service_id}/switch-plan¶
Close the current billing period and begin a new plan.
Totals reset at this boundary deliberately: a period must never span two price sets, or its cost is an average of tariffs that were never both in force. Closed periods keep their own snapshot of retailer/network/version, so history reads as what it was.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
service_id |
path | string | yes |
Request body (application/json): PlanSwitch
| Field | Type | Required | Default |
|---|---|---|---|
retailer |
string or null | no | |
network |
string or null | no |
GET /api/services/{service_id}/export¶
Export a service's tariff as a portable profile.
Identifying fields are stripped: sharing a plan should not share a meter number, an account, or a network approval that belongs to one connection.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
service_id |
path | string | yes |
POST /api/services/import¶
Validate a tariff profile, and optionally apply it.
dry_run=true (the default) reports what would happen without touching
anything — importing a tariff overwrites rates that price real money, so it
should never be a single unconfirmed click.
With service_id the profile is applied to that service, leaving its
identity and network grant intact. Without one, a new service is created.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
dry_run |
query | boolean | no |
service_id |
query | string or null | no |
Request body (application/json): TariffImportBundle
| Field | Type | Required | Default |
|---|---|---|---|
type |
string or null | no | |
version |
integer or null | no | |
exported_at |
number or null | no | |
profile |
object | no |
POST /api/tariff/validate-rates¶
Check a season/wave plan before it is saved.
Server-side because the resolver is the authority on what it will actually
price — a UI that validated separately would drift from it. Returns
{ok, problems: [...]} where problems are plain sentences.
Access: admin role.
Request body (application/json): RateValidateBody
| Field | Type | Required | Default |
|---|---|---|---|
seasons |
list of object | no | |
default_rate |
object or null | no | |
kind |
string or null | no | |
dynamic |
object or null | no |
GET /api/services/{service_id}/history-count¶
How many closed billing periods this service produced — the UI uses it to offer 'retire' instead of 'delete'.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
service_id |
path | string | yes |
GET /api/services¶
List all electricity utility services.
Access: any signed-in role.
POST /api/services¶
Create a new utility service.
Access: admin role.
Request body (application/json): ServiceCreate
| Field | Type | Required | Default |
|---|---|---|---|
name |
string | yes | |
meter_number |
string | no | |
account |
string | no | |
ac_service |
integer | no | 1 |
rated_amps |
integer | no | 0 |
PATCH /api/services/{service_id}¶
Update a utility service. exclude_unset so booleans/windows can be set to false/null explicitly (e.g. clearing a tariff window).
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
service_id |
path | string | yes |
Request body (application/json): ServiceUpdate
| Field | Type | Required | Default |
|---|---|---|---|
name |
string or null | no | |
meter_number |
string or null | no | |
account |
string or null | no | |
ac_service |
integer or null | no | |
rated_amps |
integer or null | no | |
has_tou |
boolean or null | no | |
has_peak_demand |
boolean or null | no | |
has_export_bonus |
boolean or null | no | |
min_monthly_bill |
number or null | no | |
pricing_api |
string or null | no | |
demand_window |
TariffWindow or null | no | |
bonus_window |
TariffWindow or null | no | |
pricing |
object or null | no | |
plan_type |
string or null | no | |
export_allowed |
boolean or null | no | |
solar_export_allowed |
boolean or null | no | |
battery_export_allowed |
boolean or null | no | |
export_restriction_note |
string or null | no | |
export_limit_kw |
number or null | no | |
charging_allowed |
boolean or null | no | |
discharging_allowed |
boolean or null | no | |
country |
string or null | no | |
timezone |
string or null | no | |
has_export_charge |
boolean or null | no | |
retailer |
string or null | no | |
network |
string or null | no | |
enabled |
boolean or null | no | |
pto_status |
string or null | no | |
pto_date |
number or null | no | |
pto_reference |
string or null | no |
DELETE /api/services/{service_id}¶
Delete a utility service — refused once it has produced billing history.
Deleting would strand closed periods against a service that no longer
exists, which is exactly the continuity the history view is for. Retiring
(enabled: false) stops it pricing anything while keeping its rows.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
service_id |
path | string | yes |
GET /api/gateways¶
List all gateways with live status from the registry.
Access: any signed-in role.
POST /api/gateways¶
Add a new gateway.
Access: admin role.
Request body (application/json): GatewayCreate
| Field | Type | Required | Default |
|---|---|---|---|
gateway_id |
string | yes | |
name |
string | yes | |
host |
string | no | |
port |
integer | no | 502 |
unit_id |
integer | no | 1 |
description |
string | no | |
poll_interval |
integer | no | 10 |
timeout |
number | no | 10.0 |
mock |
boolean | no | false |
device_type |
string | no | "agate" |
home_load_source |
string or null | no | |
publish_to_ha |
boolean | no | true |
GET /api/gateways/{gw_id}¶
Get a single gateway with live status.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
PATCH /api/gateways/{gw_id}¶
Update gateway configuration.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
Request body (application/json): GatewayUpdate
| Field | Type | Required | Default |
|---|---|---|---|
name |
string or null | no | |
host |
string or null | no | |
port |
integer or null | no | |
unit_id |
integer or null | no | |
description |
string or null | no | |
poll_interval |
integer or null | no | |
timeout |
number or null | no | |
enabled |
boolean or null | no | |
service_id |
string or null | no | |
phase |
string or null | no | |
phase_view |
string or null | no | |
device_type |
string or null | no | |
ac_type |
integer or null | no | |
publish_to_ha |
boolean or null | no | |
home_load_source |
string or null | no |
DELETE /api/gateways/{gw_id}¶
Remove a gateway. Default gateway cannot be removed.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
POST /api/gateways/{gw_id}/start¶
Start polling a gateway.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
POST /api/gateways/{gw_id}/stop¶
Stop polling a gateway and remember the stop across restarts.
Sets autostart=0 so the gateway is not re-started on the next app
boot. enabled stays 1 — this is a user pause, not an admin disable,
so the gateway remains configured and can be started again. This is what
keeps a stopped (esp. mock) gateway from self-restarting on reboot.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
POST /api/gateways/{gw_id}/restart¶
Tear down and recreate a gateway's Modbus connection.
Unlike /diagnose (read-only), this is the actual fix for a wedged
session: it discards the current GatewayInstance (closing whatever
socket state it had, good or bad) and reconnects from scratch, without
needing a full bridge process restart. registry.start_gateway
already stops any existing instance for this ID before starting a new
one, so this is mechanically identical to /start — this endpoint exists
for a clear, explicit "restart" action in the UI/CLI regardless of
whether the gateway looked like it was already running.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
POST /api/gateways/{gw_id}/healthcheck¶
Force an immediate TCP health probe, instead of waiting for the periodic HealthChecker (default: every 60s) to get to this gateway.
Updates the same status.health field the topbar dot reads, so the
UI reflects the result on its next refresh.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
GET /api/gateways/{gw_id}/detect-phases¶
Auto-detect the gateway's wired phase(s) from its 701 registers.
Reads the gateway's latest cached points (no extra Modbus traffic) and
infers connected/utilised phases. Also reports whether the result matches
the gateway's currently declared phase.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
POST /api/gateways/{gw_id}/test¶
Test TCP connectivity to a gateway's host:port.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
POST /api/gateways/{gw_id}/diagnose¶
Root-cause a Modbus connectivity problem for a gateway.
Runs TCP reachability probes (Modbus port + Local API port), a live Modbus protocol-level read reusing the poller's existing session (never opens a second concurrent Modbus session — see docs/vendor-issues.md on concurrent-access corruption), and cross-checks the result against the poller's own reported state to catch cases where the poller thinks it's fine but isn't.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
GET /api/gateways/{gw_id}/points¶
Get current points for a specific gateway.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
POST /api/gateways/{gw_id}/command¶
Dispatch a control command to a specific gateway.
Access: control capability.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
Request body (application/json): GatewayCommandRequest
| Field | Type | Required | Default |
|---|---|---|---|
slug |
string | yes | |
value |
string | yes |
GET /api/gateways/{gw_id}/battery/limits¶
Battery power limits a force command may use: trusted ratings only (#133).
max_charge_w / max_discharge_w are None until the rating is
calibrated or Model 702 is confirmed by an observed peak; no 5000 W default.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
GET /api/gateways/{gw_id}/battery-rating¶
The evidence behind this gateway's battery power rating, per direction (#133).
trusted says whether force commands are allowed; reason says why
not and how to fix it. calibration is the window being recorded, if any.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
POST /api/gateways/{gw_id}/battery-rating/calibration¶
Record the battery's full-rate power while the owner runs a dispatch in the app.
Start, then in the FranklinWH app run a TOU battery dispatch (force charge from grid, or force discharge to grid) with import and export unlimited, at mid SoC. Finish after at least a minute: the peak becomes the calibrated rating for that direction. The bridge sends nothing to the aGate here.
Access: control capability.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
Request body (application/json): CalibrationRequest
| Field | Type | Required | Default |
|---|---|---|---|
action |
string | yes | |
direction |
string or null | no |
GET /api/gateways/{gw_id}/dispatch-outcomes¶
Every battery command sent to this gateway and how it ended, newest first.
States: sent / failed / verified / not_effective / reasserted / released /
release_unverified / superseded (design PR #57 §5.1). requester says
who asked: schedule:<entry id> or manual.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
limit |
query | integer | no |
GET /api/gateways/{gw_id}/link-stats¶
Hourly link health for one gateway: requests by kind and code path (outcomes, latency), connections, reconnects, rescans, overlap, sockets. Read-only measurement; the baseline the connection work is judged by.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
gw_id |
path | string | yes |
hours |
query | integer | no |
Setup wizard¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/setup/state |
Get Setup State | any signed-in role |
POST |
/api/setup/state |
Post Setup State | admin role |
GET |
/api/setup/subnets |
Get Subnets | admin role |
POST |
/api/setup/scan |
Start Scan | admin role |
GET |
/api/setup/scan/{scan_id} |
Get Scan | admin role |
POST |
/api/setup/probe |
Probe Host | admin role |
POST |
/api/setup/connect |
Connect Gateway | admin role |
POST |
/api/setup/demo |
Create Demo | admin role |
POST |
/api/setup/use-ha |
Post Use Ha | admin role |
POST |
/api/setup/publish-to-ha |
Post Publish To Ha | admin role |
GET |
/api/setup/checklist |
Get Checklist | admin role |
GET |
/api/setup/accounts |
Get Accounts | admin role |
GET /api/setup/state¶
Whether the wizard should open. Any signed-in user may ask; only admins are shown the wizard (the UI checks the role).
Access: any signed-in role.
POST /api/setup/state¶
Record a choice — typically skipped, or pending to run it again.
Access: admin role.
Request body (application/json): SetupStateUpdate
| Field | Type | Required | Default |
|---|---|---|---|
state |
string | yes |
GET /api/setup/subnets¶
Subnets to search, each with where it came from (see environment.py).
Also the networks of aGates already set up here: an aGate reached over Tailscale or a VPN lives on a network the bridge's host isn't on, so the host's own interfaces never suggest it — but a second aGate is usually on the same network as the first.
Access: admin role.
POST /api/setup/scan¶
Search subnets for aGates in the background; poll GET /scan/{id}.
Access: admin role.
Request body (application/json): ScanRequest
| Field | Type | Required | Default |
|---|---|---|---|
subnets |
list of string | yes | |
port |
integer | no | 502 |
unit_id |
integer | no | 1 |
GET /api/setup/scan/{scan_id}¶
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
scan_id |
path | string | yes |
POST /api/setup/probe¶
Identify one address (the wizard's Enter address → Test).
Access: admin role.
Request body (application/json): ProbeRequest
| Field | Type | Required | Default |
|---|---|---|---|
host |
string | yes | |
port |
integer | no | 502 |
unit_id |
integer | no | 1 |
POST /api/setup/connect¶
Set up a real gateway at this address and start it.
The first one fills the primary gateway (internally still id default,
which owns the unprefixed MQTT topics existing HA setups rely on); any
further ones are added alongside.
Access: admin role.
Request body (application/json): ConnectRequest
| Field | Type | Required | Default |
|---|---|---|---|
host |
string | yes | |
port |
integer | no | 502 |
unit_id |
integer | no | 1 |
name |
string | yes | |
device_type |
string | no | "agate" |
POST /api/setup/demo¶
Create a demo (mock) gateway.
Always a separate gateway — never the primary: the primary owns the unprefixed MQTT topics, and synthetic data there would land in the same HA entities (and long-term statistics) a real aGate uses later.
Not published to Home Assistant until the user says so (#96): as an add-on a demo used to put a "Demo aGate" device into the user's real HA unasked.
Access: admin role.
Request body (application/json): DemoRequest
| Field | Type | Required | Default |
|---|---|---|---|
name |
string | no | "Demo aGate" |
ac_type |
integer | no | 0 |
POST /api/setup/use-ha¶
Record whether this install is used with Home Assistant (#94).
Not asked as an add-on, which always runs inside Home Assistant.
Access: admin role.
Request body (application/json): UseHaUpdate
| Field | Type | Required | Default |
|---|---|---|---|
use_ha |
boolean | yes |
POST /api/setup/publish-to-ha¶
The wizard's "Publish
The same switch as PATCH /api/gateways/{id} publish_to_ha, and
applied the same way — live: off removes the entities from HA, on
publishes them. Returns the checklist, which reflects the answer.
Access: admin role.
Request body (application/json): PublishToHaUpdate
| Field | Type | Required | Default |
|---|---|---|---|
gateway_id |
string | yes | |
publish |
boolean | yes |
GET /api/setup/checklist¶
Step 6: is everything between the bridge and Home Assistant working?
Each item is ok / fail / unknown (couldn't tell — shown as a
manual step) / skipped (not using Home Assistant), with a detail.
Access: admin role.
GET /api/setup/accounts¶
Logins on this bridge, and which still have a password anyone would try first ("admin", the username...). The shipped compose seeds admin/admin, so a new install starts there.
Under HA ingress the bridge's own logins aren't used: applies is false.
Access: admin role.
Feature modules¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/modules |
List Modules | any signed-in role |
PATCH |
/api/modules/{module_id} |
Toggle Module | admin role |
GET /api/modules¶
All feature modules with resolved enabled state + can_access for the
current user's role. Drives the UI nav/tab gating and the admin Modules
panel.
Access: any signed-in role.
PATCH /api/modules/{module_id}¶
Enable/disable a module (admin). Core modules can't be disabled.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
module_id |
path | string | yes |
Request body (application/json): ModuleToggle
| Field | Type | Required | Default |
|---|---|---|---|
enabled |
boolean | yes |
Energy¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/energy/flow |
Energy Flow | any signed-in role |
GET |
/api/energy/battery-hours |
Battery Hours | any signed-in role |
GET |
/api/energy/totals |
Energy Totals Endpoint | any signed-in role |
GET /api/energy/flow¶
Directional energy flows for a day (default today) or an explicit span.
day is the common case and what the dashboards use. start/end
epoch seconds cover week/month/year views without a second endpoint.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
day |
query | string or null | no |
start |
query | number or null | no |
end |
query | number or null | no |
gateway |
query | string | no |
GET /api/energy/battery-hours¶
Per-hour battery charge/discharge for a local day (default today).
Feeds the Solar Forecast card's charging/discharging band. Integrated from
the sampled battery_w series (sign-split, so a mixed hour reports both
directions) — an estimate from sampled power, not a meter reading.
Hours after now carry state: null.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
day |
query | string or null | no |
gateway |
query | string | no |
GET /api/energy/totals¶
Lifetime and period kWh, straight from the aGate's own counters.
Measured, not reconstructed — these are the figures to trust when they disagree with the Sankey's arcs.
Projected out of the same sensor snapshot the Automation Builder and the schedule engine read, rather than re-deriving the counters here. A second derivation is a second thing to drift: the whole point of these being the trustworthy numbers is that there is exactly one of them.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
gateway |
query | string | no |
Point history¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/point-history/config |
Get Config | any signed-in role |
PUT |
/api/point-history/config |
Put Config | control capability |
GET |
/api/point-history/series |
Get Series | any signed-in role |
GET |
/api/point-history/storage |
Get Storage | any signed-in role |
GET /api/point-history/config¶
Current recording settings, plus the catalogue a client can offer.
Access: any signed-in role.
PUT /api/point-history/config¶
Update recording settings.
Takes effect on the next poll tick — the loop re-reads this each pass rather than caching it, so there is nothing to restart. Shrinking retention does not delete anything here; the purge pass applies it on its own schedule.
Access: control capability.
Request body (application/json): ConfigPatch
| Field | Type | Required | Default |
|---|---|---|---|
enabled |
boolean or null | no | |
points |
list of string or null | no | |
interval_s |
integer or null | no | |
retention_days |
integer or null | no |
GET /api/point-history/series¶
Historical series for a comma-separated points list.
bucket_s is chosen automatically from the span unless given. The
response echoes the resolved span and bucket so a caller can label the
axis with what it actually got rather than what it asked for.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
points |
query | string | yes |
day |
query | string or null | no |
start |
query | number or null | no |
end |
query | number or null | no |
bucket_s |
query | number or null | no |
gateway |
query | string | no |
GET /api/point-history/storage¶
What point history costs now, and what a candidate config would cost.
The projection query params let Settings show the consequence of a change while the user is still deciding, without saving it first.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
points |
query | integer or null | no |
interval_s |
query | number or null | no |
retention_days |
query | number or null | no |
Solar forecast and location¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/solar/forecast |
Solar Forecast | any signed-in role |
GET |
/api/solar/settings |
Get Solar Settings | any signed-in role |
PUT |
/api/solar/settings |
Put Solar Settings | admin role |
POST |
/api/solar/location/from-ha |
Location From Ha | admin role |
GET /api/solar/forecast¶
Weather + PV production forecast. Needs latitude/longitude set; otherwise
{configured: false, reason}. Cached 15 min; force refetches.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
force |
query | boolean | no |
gateway |
query | string | no |
GET /api/solar/settings¶
Location + PV array settings for the Settings form.
Access: any signed-in role.
PUT /api/solar/settings¶
Update the location / PV array (only the fields sent).
Access: admin role.
Request body (application/json): SolarSettingsBody
| Field | Type | Required | Default |
|---|---|---|---|
pv_latitude |
number or null | no | |
pv_longitude |
number or null | no | |
pv_kwp |
number | no | 5.0 |
pv_tilt |
number | no | 20.0 |
pv_azimuth |
number | no | 0.0 |
POST /api/solar/location/from-ha¶
Use a configured Home Assistant's home location (its get_config
latitude/longitude) as the forecast location, and save it.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
instance |
query | string or null | no |
Tariffs and prices¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/tariff/spot |
Tariff Spot | any signed-in role |
GET |
/api/tariff/spot/settings |
Get Spot Settings | any signed-in role |
PUT |
/api/tariff/spot/settings |
Put Spot Settings | admin role |
GET |
/api/tariff/dynamic |
Tariff Dynamic | any signed-in role |
GET |
/api/tariff/overview |
Tariff Overview | any signed-in role; module energy_costs |
GET |
/api/tariff/history |
Tariff History | any signed-in role; module energy_costs |
GET |
/api/tariff/plans |
Tariff Plans | any signed-in role; module energy_costs |
POST |
/api/tariff/plans |
Create Tariff Plan | control capability; module energy_costs |
PATCH |
/api/tariff/plans/{plan_id} |
Patch Tariff Plan | control capability; module energy_costs |
GET |
/api/tariff/history.csv |
Tariff History Csv | any signed-in role; module energy_costs |
GET /api/tariff/spot¶
Live wholesale price: the AEMO NEM spot for region (default: the
configured one) and the configured HA price entities, plus the ACTIVE
buy/sell (HA wins per side over NEM). No region and nothing configured ->
spot: null, and nothing is fetched.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
region |
query | string or null | no |
force |
query | boolean | no |
GET /api/tariff/spot/settings¶
NEM region + HA price entities, for the Settings form.
Access: any signed-in role.
PUT /api/tariff/spot/settings¶
Update the NEM region / HA price entities (only the fields sent).
Access: admin role.
Request body (application/json): SpotSettingsBody
| Field | Type | Required | Default |
|---|---|---|---|
nem_region |
string or null | no | |
tariff_price_entity |
string or null | no | |
tariff_feedin_entity |
string or null | no |
GET /api/tariff/dynamic¶
The live price a dynamic plan is (or would be) priced at.
With no source: the ACTIVE plan's provider and the live price billing
is using now (live is null for a static plan). With source (nem
+ region, or ha + buy_entity [+ feedin_entity]): a preview
for the tariff editor, before anything is saved. Invalid input -> 422.
Access: any signed-in role.
| Parameter | In | Type | Required |
|---|---|---|---|
source |
query | string or null | no |
region |
query | string or null | no |
buy_entity |
query | string or null | no |
feedin_entity |
query | string or null | no |
GET /api/tariff/overview¶
Setup config + live period values + linked automations for the tab.
Access: any signed-in role; module energy_costs.
| Parameter | In | Type | Required |
|---|---|---|---|
gateway |
query | string | no |
GET /api/tariff/history¶
Closed billing periods (reporting/history), most-recent first.
Each period carries the plan(s) in force during it, so a period that straddles a retailer switch can be flagged rather than silently reported under one retailer's name.
Access: any signed-in role; module energy_costs.
| Parameter | In | Type | Required |
|---|---|---|---|
gateway |
query | string | no |
limit |
query | integer | no |
service |
query | string or null | no |
GET /api/tariff/plans¶
Plan history for a service, oldest first.
Access: any signed-in role; module energy_costs.
| Parameter | In | Type | Required |
|---|---|---|---|
service |
query | string or null | no |
POST /api/tariff/plans¶
Record a plan change, closing the plan that was in force at that date.
Inserting between two existing plans is supported — that is what recording history you did not capture at the time means.
Access: control capability; module energy_costs.
| Parameter | In | Type | Required |
|---|---|---|---|
service |
query | string or null | no |
Request body (application/json): PlanChange
| Field | Type | Required | Default |
|---|---|---|---|
valid_from |
number | yes | |
retailer |
string or null | no | |
network |
string or null | no | |
plan_type |
string or null | no | |
pricing |
string or object or null | no | |
note |
string | no |
PATCH /api/tariff/plans/{plan_id}¶
Correct a plan's start date.
Needed because a backfilled plan starts at the placeholder migration 45 had nothing better to use, leaving no earlier instant at which to insert a predecessor. The real start has to be settable first, then the earlier retailer can be recorded before it.
Access: control capability; module energy_costs.
| Parameter | In | Type | Required |
|---|---|---|---|
plan_id |
path | string | yes |
Request body (application/json): PlanStartCorrection
| Field | Type | Required | Default |
|---|---|---|---|
valid_from |
number | yes |
GET /api/tariff/history.csv¶
Closed billing periods as a CSV download (chronological).
Access: any signed-in role; module energy_costs.
| Parameter | In | Type | Required |
|---|---|---|---|
gateway |
query | string | no |
Legal notice¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/disclaimer |
Get Disclaimer | any signed-in role |
POST |
/api/disclaimer |
Ack Disclaimer | any signed-in role |
GET |
/api/disclaimer/acks |
List Acks | admin role |
GET /api/disclaimer¶
The notice text, and whether this user has already accepted this version.
Returns the text as well as the flag so the UI never keeps its own copy of the wording — one source, and a version bump can't leave a stale paragraph rendered next to a fresh version number.
Access: any signed-in role.
POST /api/disclaimer¶
Record acceptance. Declining is answered honestly rather than stored.
Access: any signed-in role.
Request body (application/json): AckRequest
| Field | Type | Required | Default |
|---|---|---|---|
agreed |
boolean | no | true |
GET /api/disclaimer/acks¶
The acknowledgement trail. Admin-only — it is a record of who agreed to what, which is nobody else's business.
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
limit |
query | integer | no |
Users¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/users |
List Users | admin role |
POST |
/api/users |
Create New User | admin role |
PATCH |
/api/users/{user_id} |
Update Existing User | admin role |
DELETE |
/api/users/{user_id} |
Remove User | admin role |
GET /api/users¶
Access: admin role.
POST /api/users¶
Access: admin role.
Request body (application/json): UserCreate
| Field | Type | Required | Default |
|---|---|---|---|
username |
string | yes | |
password |
string | yes | |
role |
string | no | "viewer" |
expires_at |
number or null | no |
PATCH /api/users/{user_id}¶
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
user_id |
path | string | yes |
Request body (application/json): UserUpdate
| Field | Type | Required | Default |
|---|---|---|---|
role |
string or null | no | |
enabled |
boolean or null | no | |
password |
string or null | no | |
expires_at |
number or null | no |
DELETE /api/users/{user_id}¶
Access: admin role.
| Parameter | In | Type | Required |
|---|---|---|---|
user_id |
path | string | yes |
Support¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/support/info |
Get Support Info | admin role |
GET /api/support/info¶
Redacted summary of this install, as JSON and as Markdown.
No serials, IPs, host names, ports, MAC addresses, location, usernames, emails, HA URLs or tokens, passwords, MQTT credentials or client id, or device names. Log messages are scrubbed by pattern — review before posting.
Access: admin role.
Schedules¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/schedules |
List Schedules | any signed-in role; module automations |
POST |
/api/schedules |
Add Schedule | admin role; module automations |
GET |
/api/schedules/export |
Export Schedules | any signed-in role; module automations |
POST |
/api/schedules/import |
Import Schedules | admin role; module automations |
GET |
/api/schedules/log |
Schedule Log | any signed-in role; module automations |
POST |
/api/schedules/{schedule_id}/execute |
Execute Schedule | admin role; module automations |
POST |
/api/schedules/{schedule_id}/stop |
Stop Schedule | admin role; module automations |
GET |
/api/schedules/timeline |
Schedule Timeline | any signed-in role; module automations |
GET |
/api/schedules/{schedule_id} |
Get Single Schedule | any signed-in role; module automations |
PATCH |
/api/schedules/{schedule_id} |
Patch Schedule | admin role; module automations |
DELETE |
/api/schedules/{schedule_id} |
Remove Schedule | admin role; module automations |
GET |
/api/schedule/actions |
List Schedule Actions | any signed-in role; module automations |
GET /api/schedules¶
List all schedule entries with active-now / next-fire previews.
Access: any signed-in role; module automations.
POST /api/schedules¶
Create a schedule entry.
Access: admin role; module automations.
Request body (application/json): ScheduleCreate
| Field | Type | Required | Default |
|---|---|---|---|
name |
string | yes | |
when_spec |
WhenSpec | no | |
action |
string | yes | |
params |
object | no | |
target_type |
string | no | "gateway" |
target_id |
string or null | no | |
enabled |
boolean | no | true |
release |
string | no | "release" |
conflict |
string | no | "defer" |
priority |
integer | no | 0 |
trigger_kind |
string or null | no | |
trigger_spec |
object | no | |
entry_conditions |
object or null | no | |
exit_conditions |
object or null | no | |
duration_s |
integer or null | no | |
release_policy |
string | no | "release" |
missed_policy |
string | no | "late_fire_remaining" |
entry_hold_s |
integer | no | 0 |
ha_actions |
list of HaActionItem | no |
GET /api/schedules/export¶
Export schedules as a portable JSON bundle. ids = comma-separated
entry ids (default: all).
Access: any signed-in role; module automations.
| Parameter | In | Type | Required |
|---|---|---|---|
ids |
query | string or null | no |
POST /api/schedules/import¶
Validate (and optionally create) an imported bundle. dry_run=true
(default) returns a per-entry validation report without creating anything;
dry_run=false creates the valid entries DISABLED (for review), with any
unknown gateway target falling back to Default.
Access: admin role; module automations.
| Parameter | In | Type | Required |
|---|---|---|---|
dry_run |
query | boolean | no |
Request body (application/json): ImportBundle
| Field | Type | Required | Default |
|---|---|---|---|
type |
string or null | no | |
version |
integer or null | no | |
entries |
list of object | no |
GET /api/schedules/log¶
Recent schedule dispatch/audit events (newest first).
Optional filters: schedule_id (one entry's runs) and status (the
audit result: fired/gated/missed/executed/exit_condition_met/…) — this backs
the FWHAI audit + history views.
Access: any signed-in role; module automations.
| Parameter | In | Type | Required |
|---|---|---|---|
limit |
query | integer | no |
schedule_id |
query | string or null | no |
status |
query | string or null | no |
POST /api/schedules/{schedule_id}/execute¶
Fire a schedule entry's action now (respects entry_conditions unless
?force=true). The dispatch auto-releases after the entry's duration.
Access: admin role; module automations.
| Parameter | In | Type | Required |
|---|---|---|---|
schedule_id |
path | string | yes |
force |
query | boolean | no |
POST /api/schedules/{schedule_id}/stop¶
Gracefully stop a schedule's CURRENT run: release the live dispatch it owns and skip re-firing until the next scheduled window. The entry stays enabled (disable it to stop permanently).
Access: admin role; module automations.
| Parameter | In | Type | Required |
|---|---|---|---|
schedule_id |
path | string | yes |
GET /api/schedules/timeline¶
Return the day's windows per entry for the visual timeline.
day is an optional weekday (0=Mon..6=Sun) to preview; defaults to today.
Each segment is minutes-from-midnight so the UI can lay out a 24h bar.
Access: any signed-in role; module automations.
| Parameter | In | Type | Required |
|---|---|---|---|
day |
query | integer or null | no |
GET /api/schedules/{schedule_id}¶
Get one schedule entry.
Access: any signed-in role; module automations.
| Parameter | In | Type | Required |
|---|---|---|---|
schedule_id |
path | string | yes |
PATCH /api/schedules/{schedule_id}¶
Update a schedule entry.
Uses exclude_unset so we apply exactly the fields the client sent —
including explicit nulls. A previous if v is not None filter silently
dropped nulls, which made it impossible to CLEAR a field: deleting all
exit_conditions (client sends exit_conditions: null) never persisted.
update_schedule stores None as SQL NULL for the nullable JSON columns.
Access: admin role; module automations.
| Parameter | In | Type | Required |
|---|---|---|---|
schedule_id |
path | string | yes |
Request body (application/json): ScheduleUpdate
| Field | Type | Required | Default |
|---|---|---|---|
name |
string or null | no | |
when_spec |
WhenSpec or null | no | |
action |
string or null | no | |
params |
object or null | no | |
target_type |
string or null | no | |
target_id |
string or null | no | |
enabled |
boolean or null | no | |
release |
string or null | no | |
conflict |
string or null | no | |
priority |
integer or null | no | |
trigger_kind |
string or null | no | |
trigger_spec |
object or null | no | |
entry_conditions |
object or null | no | |
exit_conditions |
object or null | no | |
duration_s |
integer or null | no | |
release_policy |
string or null | no | |
missed_policy |
string or null | no | |
entry_hold_s |
integer or null | no | |
ha_actions |
list of HaActionItem or null | no |
DELETE /api/schedules/{schedule_id}¶
Delete a schedule entry.
Access: admin role; module automations.
| Parameter | In | Type | Required |
|---|---|---|---|
schedule_id |
path | string | yes |
GET /api/schedule/actions¶
The command vocabulary a schedule entry can dispatch (for the UI form).
Access: any signed-in role; module automations.
Scheduler sensors and conditions¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/sensors |
List Sensors | any signed-in role; module automations |
GET |
/api/automation/constants |
Get Constants | any signed-in role; module automations |
PUT |
/api/automation/constants |
Put Constants | admin role; module automations |
POST |
/api/scheduler/evaluate |
Evaluate Conditions | admin role; module automations |
GET /api/sensors¶
Sensor namespace + current values for the condition-builder dropdowns.
Includes the static Bridge sensors plus any HA entities (ha:<inst>:<entity>)
so the Automation Builder can condition on Home Assistant state.
Access: any signed-in role; module automations.
| Parameter | In | Type | Required |
|---|---|---|---|
gateway |
query | string | no |
GET /api/automation/constants¶
User-defined automation constants (min/max/demand SoC) + their [min,max]
bounds for the settings form. Surfaced as const.* sensors too.
Access: any signed-in role; module automations.
PUT /api/automation/constants¶
Update automation constants (only sent fields; each clamped to [0,100]).
Access: admin role; module automations.
Request body (application/json): ConstantsBody
| Field | Type | Required | Default |
|---|---|---|---|
min_discharge_soc |
number or null | no | |
max_charge_soc |
number or null | no | |
demand_charge_min_soc |
number or null | no |
POST /api/scheduler/evaluate¶
Evaluate a condition tree against the gateway's live snapshot.
Returns {result: bool, per_condition: [{sensor, op, value, live_value,
result}]} — the Test Verification response.
Access: admin role; module automations.
| Parameter | In | Type | Required |
|---|---|---|---|
gateway |
query | string | no |
Request body (application/json): ConditionTreeBody
| Field | Type | Required | Default |
|---|---|---|---|
match |
string | no | "ALL" |
conditions |
list of object | no |
Home Assistant instances and entities¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/api/ha/instances |
List Instances | any signed-in role; module ha_entities |
POST |
/api/ha/instances |
Create Instance | admin role; module ha_entities |
PATCH |
/api/ha/instances/{ha_id} |
Patch Instance | admin role; module ha_entities |
DELETE |
/api/ha/instances/{ha_id} |
Remove Instance | admin role; module ha_entities |
POST |
/api/ha/test |
Test Connection | admin role; module ha_entities |
GET |
/api/ha/notify-targets |
Notify Targets | any signed-in role; module ha_entities |
GET |
/api/ha/entities |
Browse Entities | any signed-in role; module ha_entities |
GET |
/api/ha/domains |
List Domains | any signed-in role; module ha_entities |
POST |
/api/ha/entities/expose |
Expose Entity | admin role; module ha_entities |
GET |
/api/ha/dashboard-yaml |
Dashboard Yaml | any signed-in role; module ha_entities |
GET /api/ha/instances¶
Configured HA instances (tokens redacted) + live connection status.
Access: any signed-in role; module ha_entities.
POST /api/ha/instances¶
Access: admin role; module ha_entities.
Request body (application/json): HaInstanceCreate
| Field | Type | Required | Default |
|---|---|---|---|
name |
string | yes | |
base_url |
string | yes | |
token |
string or null | no | |
is_default |
boolean | no | false |
enabled |
boolean | no | true |
PATCH /api/ha/instances/{ha_id}¶
Access: admin role; module ha_entities.
| Parameter | In | Type | Required |
|---|---|---|---|
ha_id |
path | string | yes |
Request body (application/json): HaInstanceUpdate
| Field | Type | Required | Default |
|---|---|---|---|
name |
string or null | no | |
base_url |
string or null | no | |
token |
string or null | no | |
is_default |
boolean or null | no | |
enabled |
boolean or null | no |
DELETE /api/ha/instances/{ha_id}¶
Access: admin role; module ha_entities.
| Parameter | In | Type | Required |
|---|---|---|---|
ha_id |
path | string | yes |
POST /api/ha/test¶
Probe a base_url/token WITHOUT saving — powers the config form's Test button.
On an edit the token field is blank; if ha_id is given we fall back to
that instance's stored token so Test reflects the real (saved) credentials
instead of failing 401 with no auth.
Returns {connected, entity_count, last_error}.
Access: admin role; module ha_entities.
Request body (application/json): HaTestBody
| Field | Type | Required | Default |
|---|---|---|---|
base_url |
string | yes | |
token |
string or null | no | |
ha_id |
string or null | no |
GET /api/ha/notify-targets¶
Available notify.* services across all HA instances.
Notify targets are services, not entities, so they never appear in the entity browser — the action picker needs them from here.
Access: any signed-in role; module ha_entities.
GET /api/ha/entities¶
Browse ALL HA entities across instances (with an exposed flag),
filtered + paginated — backs the HA Entities tab.
Filters: instance (id), domain, search (substring of entity_id or
friendly name), exposed (true/false). Returns
{total, page, page_size, exposed_count, entities:[...]}.
search accepts comma-separated terms matched as OR — real
installations name the same concept a dozen ways ("pv", "solar",
"enphase", "envoy"), so a single substring misses most of a topic. A
plain term without commas behaves exactly as before.
Access: any signed-in role; module ha_entities.
| Parameter | In | Type | Required |
|---|---|---|---|
instance |
query | string or null | no |
domain |
query | string or null | no |
search |
query | string or null | no |
exposed |
query | boolean or null | no |
page |
query | integer | no |
page_size |
query | integer | no |
GET /api/ha/domains¶
Distinct entity domains across all instances (for the filter dropdown).
Access: any signed-in role; module ha_entities.
POST /api/ha/entities/expose¶
Add/remove an entity from an instance's allowlist (persists + applies live).
Access: admin role; module ha_entities.
Request body (application/json): HaExposeBody
| Field | Type | Required | Default |
|---|---|---|---|
instance_id |
string | yes | |
entity_id |
string | yes | |
exposed |
boolean | yes |
GET /api/ha/dashboard-yaml¶
Copy-paste HA dashboard card YAML for this bridge's entities (#86).
Entity ids come only from the chosen HA instance's device + entity
registries — never predicted. Returns {yaml, entity_count, devices,
instance, layout}; 409/503 with a detail (and no YAML) when the
registry can't be read or holds none of our entities.
Access: any signed-in role; module ha_entities.
| Parameter | In | Type | Required |
|---|---|---|---|
instance |
query | string or null | no |
layout |
query | string | no |
diagnostics |
query | boolean | no |
Web UI and legacy command¶
| Method | Path | Summary | Access |
|---|---|---|---|
GET |
/ |
Index | open |
GET |
/user |
User Dashboard | open |
GET |
/login |
Login Page | open |
POST |
/api/command |
Send Command | control capability |
POST |
/api/ui/connection-outage |
Report Connection Outage | any signed-in role |
GET /¶
Serve the main SPA shell. Unauthenticated → /login; non-admin → /user; an api account gets the "REST API only" page.
Access: open.
GET /user¶
The simplified, mobile-first end-user dashboard. Any signed-in role may view it (admins can preview it here); unauthenticated → /login.
Access: open.
GET /login¶
The login form. Already-authenticated users go to their role's home.
Access: open.
POST /api/command¶
Dispatch a control command (same path as MQTT commands).
Defaults to the "default" gateway. Use POST /api/gateways/{gw_id}/command for gateway-specific commands (added in MG-3).
Access: control capability.
Request body (application/json): CommandRequest
| Field | Type | Required | Default |
|---|---|---|---|
slug |
string | yes | |
value |
string | yes |
POST /api/ui/connection-outage¶
Log an outage the browser saw (it couldn't reach the bridge).
Access: any signed-in role.
Request body (application/json): ConnectionOutage
| Field | Type | Required | Default |
|---|---|---|---|
started_at |
number | yes | |
ended_at |
number | yes | |
failures |
integer | no | 0 |
message |
string | no | |
url |
string | no | |
device_offline |
boolean | no | false |
page |
string | no |