Skip to content

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.

Group Operations
Health and status 3
Authentication and sessions 8
Admin: points, metrics, logs, sequences, backup 35
MQTT publishing 11
Publishing groups 14
Site, services and gateways 33
Setup wizard 12
Feature modules 2
Energy 3
Point history 4
Solar forecast and location 4
Tariffs and prices 10
Legal notice 3
Users 4
Support 1
Schedules 12
Scheduler sensors and conditions 4
Home Assistant instances and entities 10
Web UI and legacy command 5

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

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 's entities to Home Assistant?" (#96).

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
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