Salia REST API
REST API for integrating with Salia AC charging stations by eCharge Hardy Barth.
Overview
The Salia REST API provides a unified HTTP interface for reading charging station state and controlling charging behavior. All state is backed by an internal MQTT broker — the REST API acts as a bridge that translates HTTP requests into MQTT topic reads and writes.
How It Works
Reading data: Send a GET request to /api/ to retrieve the full station snapshot,
or use the ?path= query parameter to navigate to a specific value
(e.g., GET /api/?path=secc/port0/salia/chargemode returns just the charge mode).
Individual sub-path endpoints (e.g., /api/secc/port0/salia/chargemode) also work and
return the value at that position in the JSON tree.
Writing data: All writes go to the single endpoint PUT /api/ (or
POST /api/). The body is a JSON object whose keys are MQTT topic paths
(without the port0/ prefix) and whose values are the settings to publish.
Multiple topics can be set in a single request.
Important: Sub-path URLs like
PUT /api/secc/port0/salia/chargemodeare not real endpoints on the server. The per-endpointput:blocks documented below are a catalog of accepted topics and expected value shapes — they describe what to put in the body ofPUT /api/, not separate routes. Writing to a sub-path URL will either be routed equivalently by path normalization or silently have no effect, depending on the web-server configuration.
Example:
Code
Unknown or non-whitelisted topics in the body are silently ignored; the
response is still {"result":"ok"}. If the body is not valid JSON, the
server returns HTTP 200 with {"error":1, "result":"no valid JSON string"}.
Cache behavior: By default, GET /api/ returns a cached snapshot from the last read.
To force a fresh read from the MQTT broker, add ?reset (e.g., GET /api/?reset).
The fresh read has a 2-second timeout, so it is slower but guarantees up-to-date data.
Authentication
Write operations (PUT/POST) require authentication via an API key passed in the
Apikey HTTP header. Requests originating on the device itself (where the client IP
matches the server's own bind address — typically 127.0.0.1) bypass authentication.
Read operations (GET) do not require authentication.
Firmware compatibility: The
Apikeyheader is only enforced on firmware 2.3.81 and newer. On older firmware versions, write operations are accepted without any authentication header — theApikeyfield is simply ignored. Integrators targeting a mixed fleet should always send the header (it is harmless on older firmware) and should detect the firmware version viaGET /api/device/software_versionif they need to condition behavior on it. Deployments still running pre-2.3.81 should plan to upgrade, as they are effectively unauthenticated.
Important Notes
- All values are returned and accepted as strings, even when representing numbers or booleans.
- Current values in Amperes are integers (e.g.,
"16", not"16.0"), except foroffered_current_limitwhich uses one decimal (e.g.,"6.0"). - Temperature values are in millidegrees Celsius (e.g.,
"49896"= 49.896°C).
Metering units
Metering values are not uniformly scaled. Each topic uses the unit documented on its endpoint. Summary:
| Topic suffix | Unit | Convert to SI |
|---|---|---|
metering/power/active_total/actual, active/ac/l{1,2,3}/actual | deci-watts (0.1 W) | divide by 10 for W |
metering/current/ac/l{1,2,3}/actual | milliamps | divide by 1000 for A |
metering/voltage/ac/l{1,2,3}/actual | centivolts (0.01 V)¹ | divide by 100 for V |
metering/energy/active_{total,import,export}/actual | watt-hours (Wh) | divide by 1000 for kWh |
metering/mains_frequency/actual | millihertz | divide by 1000 for Hz |
metering/power_factor/ac/actual | per-mille (×1000)¹ | divide by 1000 for a unitless PF |
¹ Voltage and power-factor topics are passed through from the MQTT publisher without scaling in the REST layer; the units listed are the observed raw values. Confirm with your specific meter model if in doubt.
Current limit composition
The station does not have a single current-limit input. Instead, the offered current is computed as the minimum across three independent lanes, each owned by a different source:| Lane | Topic | Written by | Active when |
|---|---|---|---|
| External | salia/extctrl_limit (alias: grid_current_limit) | External controllers — EMS / FEMS / OCPP / home-automation | Always |
| Internal | salia/intctrl_limit | The station's own charge controller (eco solar tracking, heartbeat fallback, phase-switch cooldown, etc.) | Always |
| Manual | salia/manctrl_limit | The built-in web UI in manual charge mode | Always |
| LMD | ci/global/dyn_current_limit/lmd_limit (set via salia/LM/internal/max_amp) | Internal Load Management, when enabled | Only when Internal LM is active (see Load Management tag) |
ci/global/dyn_current_limit/{external,internal,manual,lmd}_limit
and the effective limit is the minimum across all active lanes.
Additional bounds (physical hardware limit, configured max_amp,
configured socketmaxamp, cable current limit) further cap the result.
Troubleshooting tip: If raising grid_current_limit does not raise
the actually offered current, one of the other lanes is lower. The most
common culprit is intctrl_limit, which the internal regulator may have
pushed down. Read all three topics to see which one is binding; if
needed, override intctrl_limit directly (see its PUT endpoint).Detecting whether phase switching is available
Phase switching is not supported on all hardware configurations, and may also be disabled in the station's configuration even on capable hardware. When unavailable, the station does not publish any of thephase_switching/* MQTT topics at all — which means the entire
phase_switching subtree is absent from the snapshot, rather than
being present with some "disabled" marker value. There is no dedicated
"is phase switching available" flag.
The canonical detection pattern is to check the snapshot for the
presence of the subtree before issuing any phase-switching call:
Equivalent one-shot check against a specific sub-key:Code
Do not confuseCode
phase_switching/actual (current 1- or 3-phase
selection — only present if switching is available) with
ci/evse/basic/phase_count (how many phases the station is wired
for — always present, static hardware property). A station can be
wired for 3 phases but have switching disabled; in that case
phase_count is "3" and the phase_switching object is still
absent.
If phase switching is not available:
- The physical phase configuration is fixed by wiring.
setphasewrites are accepted by the REST layer but discarded internally with no effect.- Switching into
powercharge mode does not force 3-phase (the automaticsetphase=3side effect is gated on the same availability flag).
Dual-pole stations (Doppellader)
Dual-pole Salia stations ship as aSalia PLCC Master
(product 2310006) paired with a Salia PLCC Slave
(product 2310007). The two poles do not have symmetric
phase-switching capabilities out of the box:
- Slave pole — phase switching is enabled by factory
default. The
phase_switchingsubtree is present in the snapshot andsetphaseworks. - Master pole — phase switching is disabled by factory
default. The
phase_switchingsubtree is therefore absent from the snapshot, and the canonical detection check above will (correctly) report "not available". On a typical Doppellader installation, only the slave pole is intended to perform 1↔3 phase switching.
GET /api/device/modelname ("Salia PLCC Master" vs
"Salia PLCC Slave") or GET /api/device/product ("2310006"
vs "2310007"), but the presence of the phase_switching
subtree remains the authoritative runtime test — the field
configuration can be changed in the field and the subtree
reflects the live state, while the model / product strings
reflect only the hardware variant.
Note: this hardware "Master / Slave" distinction refers to the
two physical poles of a dual-pole station and is unrelated
to the cluster role (standalone / master / slave)
configured via salia/setmasterip — see Load Management
→ Station role. The two concepts share a name but are
orthogonal.
Sequencing rule
After triggering a phase switch, you must pollphase_switching/status until it returns "idle" before setting
grid_current_limit or other current limits. Failing to wait will
block charging.1. Station role (standalone / master / slave)
Defines how this physical station relates to other Salia stations on the same network. Configured via thesetmasterip write endpoint and
persisted across reboots.
standalone— default; no coupling to other stations.master— this station coordinates a cluster of slaves; RFID tag lists and protection settings are broadcast to slaves.slave— this station receives configuration from the master at the given IP; local RFID and protection changes are overwritten by master syncs.
role, masterip, sync_rfidtags (0 or 290),
and sync_protection (0 or 290) in the persisted configuration. See
the salia/setmasterip write for the exact message format.
2. Internal Load Management (LMinternal)
Dynamic current sharing between the two charging sockets on a single station (or between this station and one slave station). Configured via thesalia/LMinternal write and persisted across reboots.
Side effects when enabled: the charge controller honors a new
fourth current-limit lane, ci/global/dyn_current_limit/lmd_limit, in
addition to the external / internal / manual lanes documented under
Charging Control → Current limit composition.
The effective offered current is the minimum across all four lanes.
Writing salia/LM/internal/max_amp is the primary way to set the LMD
lane, and also clamps and republishes port0/salia/max_amp (the
station's configured maximum), so an enabled LM setup effectively
reshapes both the per-pole cap and the dynamic floor the controller
regulates against.
This means: enabling internal LM can lower the offered current even
if the client never writes LM/internal/max_amp explicitly — the
LMD lane defaults to whatever the master decided during its last
allocation cycle. If you see charging suddenly capped after enabling
LM, inspect LM/internal/status and the ci/global/dyn_current_limit/*
topics to find the binding lane.
3. Time control (Zeitsteuerung)
Scheduled charging windows configured in the web UI. Exposed read-only here viatc_enabled (on/off flag) and limit_tc (the effective
current limit imposed by the active schedule, in Amperes; equals
max_amp when no schedule is restricting).
Time control is orthogonal to the other two features — it can be on or
off regardless of role or internal-LM state. It does not introduce
an additional lane in the current-limit composer; its effect shows up
via the regular internal lane.
