← Web API - GET | Contents | Next: Sensor Calibration →
9. Web API — POST Endpoints
Control and write endpoints. Read the GET reference §8.1 first — the base URL, external-UI, CORS, auth, compile-profile, and C2-client rules apply here too.
9.1 Conventions
- Content-Type:
application/jsonfor endpoints with a body. The browser sends anOPTIONSpreflight first; the server answers204with CORS headers. - Response envelope: success is
{ "success": true, "message": "…" }at200; errors are{ "error": "…" }(missing body →400 {"error":"Missing request body"}, bad JSON →400 {"error":"Invalid JSON"}). Some legacy sections show{"status":"ok"|"error"}— treat thesuccess/errorform as authoritative. - Persistence: config/WiFi/hardware writes are saved to NVS and survive reboot.
- Async operations return
202and finish in the background — see §9.3.
9.2 Configuration & Control
POST /api/config
Update any subset of control parameters (only the fields you send change).
curl -X POST http://192.168.4.1/api/config \
-H "Content-Type: application/json" \
-d '{"control_gain": 250.0, "balance_threshold": 15.0}'Body fields (all optional): control_gain (10–1000) · balance_threshold (W, 0–100) ·
grid_current_limit (A, 0–100 — the GRID_LIMIT cap) · voltage_coef · current_coef ·
current_threshold · power_threshold. → 200 {"success":true,"message":"Configuration updated"}.
POST /api/config/reset
Reset all configuration to factory defaults (erases custom settings, writes defaults to NVS).
curl -X POST http://192.168.4.1/api/config/reset → 200 {"success":true,"message":"Configuration reset to defaults"}.
POST /api/mode
Set the operating mode.
curl -X POST http://192.168.4.1/api/mode -H "Content-Type: application/json" -d '{"mode": "auto"}'mode—off·auto·eco·offgrid·manual·boost·grid_limitgrid_limitcaps grid draw atgrid_current_limitA (current-only, no export) — set the cap withPOST /api/config {"grid_current_limit": <A>}.- Invalid value →
400 {"error":"Invalid mode (use: off, auto, eco, offgrid, manual, boost, grid_limit)"}.
POST /api/manual
Set the manual level and switch to MANUAL mode in one call.
-d '{"value": 50}' (value 0–100%) → 200 {"success":true,"message":"Manual control set"}.
(POST /api/dimmer {"value":75} does the same and also switches to MANUAL.)
9.3 Commissioning
Setting up modules the first time. See the Commissioning guide for the full walkthrough.
POST /api/modules/rescan — async
On-demand I2C scan + non-destructive reconcile (matches transport+addr → keeps config; new → added; missing → marked offline but kept; never deletes).
- Returns
202 {"scanning":true}immediately (the ~2.5 s scan runs in a worker task). - A second rescan while busy →
409 {"error":"busy","operation":"rescan"}. - Poll
GET /api/modules— when its"scanning"isfalse, the list is fresh.
POST /api/modules/role
Assign a per-channel role via the registry (the role source of truth). This is the recommended
way to bind a module — assigning dimmer auto-binds a DimmerLink to its output.
{ "addr": "0x51", "channel": 0, "role": "grid" }role—grid|solar|load|voltage|dimmer|relay|none- →
200 {"success":true,"message":"Role saved"}· no module at addr →404· bad channel →400.
POST /api/rbamp/modules/address — async
Change an rbAmp module's I2C address (two-phase commit; role mapping migrates and persists).
{ "addr": "0x50", "new_addr": "0x52" }- →
202 {"success":true,"pending":true,"new_addr":"0x52", "message":"applies after reset; module re-appears at new_addr"} 400new_addr outside 0x08–0x77 / equal to addr ·404no module at addr ·409target already a known rbAmp, or a change is already pending.
POST /api/rbamp/modules/ct-model — async
Set the rbAmp SCT-013 CT model (channel 0). Verify-then-set.
{ "addr": "0x51", "ct_model": "sct013-030" }ct_model= a catalogidfromGET /api/rbamp/ct-models.- →
202 {"success":true,"pending":true,"ct_model":"sct013-030", …}. 400unknown ct_model ·404no module ·409model not available on this firmware / change pending.
⚠️ Changing the CT model reloads the preset gain and overwrites the module's per-unit factory gain calibration. Re-selecting the same model is a no-op (protected).
9.4 Hardware
POST /api/hardware/config
Replace hardware configuration (all fields optional). Written to NVS; a reboot is required to apply.
{ "dimmer_ch1": { "gpio": 19, "enabled": true },
"zerocross_gpio": 18, "zerocross_enabled": true,
"relay_ch1": { "gpio": 15, "active_high": true, "enabled": false },
"led_status_gpio": 17, "led_load_gpio": 5 }- →
200 {"success":true,"message":"Configuration saved to NVS (reboot required)"}· validation fail →400· NVS fail →500. - To set the I2C bus pins, send
{"i2c":{"bus0":{"sda":N,"scl":M,"enabled":true}}}(see the Hardware Guide). The legacyadc_channels[]field is accepted but ignored (the ADC pipeline is removed).
POST /api/hardware/validate
🚧 Stub in this release — the body is read but not applied; validation runs against a default config. Do not rely on field-level validation yet.
9.5 Dimmers & Relays
POST /api/dimmers/all-on · /api/dimmers/all-off
Set all enabled dimmers to 100% / 0%.
→ 200 {"success":true,"dimmers":[{"dimmer_id":4,"level":100,"success":true}]}.
Per-dimmer routes
/api/dimmers/{0-3}/leveland/configwere removed — dimmer level is driven through the router mode (/api/mode,/api/manual), not per-dimmer.
POST /api/relays/{id}/on · /api/relays/{id}/off
Turn relay {id} (0–3) on/off. Optional body {"force": true} (on only).
→ 200 {"relay_id":0,"state":"on","success":true} · debounce → 400 {…"state":"debounce"…}.
Also /api/relays/all-on · /api/relays/all-off.
POST /api/relays/{id}/config
Configure a relay (all fields optional): name, gpio, power_w, min_on, min_off,
active_high, enabled, priority. → 200 {"success":true,"message":"Relay configuration saved"}.
9.6 WiFi
POST /api/wifi/connect
Connect and save credentials to NVS (auto-connect next boot; AP stays up).
curl -X POST http://192.168.4.1/api/wifi/connect -H "Content-Type: application/json" \
-d '{"ssid": "MyHomeNetwork", "password": "MyPassword123"}'passwordoptional for open networks. Success →200 {"success":true,"message":"Connected to …","ip":"…"}; failure →400. Credentials are sent in plaintext (LAN).
POST /api/wifi/disconnect · /api/wifi/forget
disconnect → AP-only, credentials kept. forget → clears saved credentials (no auto-connect next boot).
9.7 Integrations
POST /api/mqtt/config
Update MQTT config (all optional): broker, username, password, device_id, device_name,
publish_interval, ha_discovery, enabled. → 200 {"success":true,"message":"MQTT configuration saved"}.
If enabled and disconnected, it auto-connects.
POST /api/mqtt/reconnect · /api/mqtt/publish
reconnect → {"success":true,"message":"Reconnection initiated"} (400 if MQTT disabled).
publish → force-publish all topics now (400 if not connected).
Headless (C2-MQTT) and any MQTT build can be configured over MQTT — see the MQTT Guide (config-over-MQTT topics).
POST /api/ntp/config · /api/ntp/sync
config (optional ntp_server, timezone, gmt_offset_sec, daylight_offset_sec) →
200 {"success":true,"message":"NTP configuration updated and saved"}. sync forces a sync now.
POST /api/ota/update-github
Download + flash firmware from a GitHub asset URL, then reboot. Body {"url": "<asset-url>"} (required).
→ 200 {"success":true,"message":"OTA update started. Device will reboot after download."} (sent
before flashing; no second response). Gate on features.ota / features.github_ota.
Raw-binary OTA:
POST /ota/uploadtakes the raw firmware image (Content-Type: application/octet-stream, not multipart). Large OTA over WiFi is impractical on the ESP32-C2 — use serial flash on the C2.
9.8 System
POST /api/web/config
Set the external app URL (redirect target + CORS origin). {"app_url": "http://host:port"}; empty
clears it (open). Serial equivalent: web-url set <url> | clear | show.
POST /api/system/reboot
{"success":true,"message":"Rebooting in 3 seconds..."} — the device restarts ~3.5 s after replying.
9.9 Advanced / Developer (optional)
Not for normal use:
- POST /api/sim/inject / /api/sim/stop — Tier-0 test harness that injects a synthetic power
event (HTTP builds only; for bench/CI).
injectbody:{"role":"grid","current":9.0,"voltage":230.0,"power":-2000.0,"latch":false}. - POST /api/dimmerlink/devices, /api/dimmerlink/devices/address — low-level DimmerLink slot registration/addressing (prefer role assignment above).
- POST /api/espnow/nodes — assign a role to an ESP-NOW node by MAC.
- POST /api/rbamp/rescan — rbAmp-only rescan (
501when autodiscovery is off, e.g. default on C2). - POST /api/calibrate — 🚧 not implemented (
501).
9.10 Removed in v2.0
- Per-GPIO dimmer routes
/api/dimmers/{0-3}/leveland/config— dimming is via router mode now. adc_channels[]in/api/hardware/config— accepted but ignored (ADC pipeline removed).- Sensor-driver routes
/api/hardware/sensor-profiles,/sensor-types,/voltage-drivers,/current-drivers— smart modules self-calibrate; use/api/modules/role+/api/rbamp/modules/ct-model.