# Write-Method Endpoints (POST / PUT / PATCH / DELETE) All endpoints that mutate state, sorted by risk. Sources: - Static analysis of `/min/production.min.js` (8MB) and `/linker/js/esbuild-bundle.js` (16MB) - Socket.io call extraction from both bundles - `inputs/lrpc_post.json` Safari capture (real POST payloads) **Do not call destructive or control endpoints without intent.** Safe read-only probing stays on GET. --- ## Device control (high impact — sends LoRa radio commands) ### Pool controller impulse / start / stop ``` POST /remote/module/control Content-Type: application/json { "moduleSerialNumber": "150302F1DA270001", "linesControl": [ { "index": 0, "action": 0 } // stop output { "index": 1, "action": 1 } // start (indefinitely) { "index": 1, "action": 2, "time": "30:00" } // timed impulse MM:SS { "index": 0, "action": 3, "time": "01:00" } // boost filtration { "index": 0, "action": 5, "manualDuration": 1 } // pause (?) { "index": 0, "action": 11, "time": "...", "speed": 1 } // backwash ] } ``` Verified real payloads (mitmproxy captures): - `{"action":0,"index":0}` → stop fill valve (LRLEVEL serial `3A01020D2ED90001`) - `{"action":1,"index":1}` → Station 2, start indefinitely - `{"action":2,"time":"30:00","index":1}` → Station 2, 30 min impulse - `{"action":3,"time":"01:00","index":0}` → boost filtration 1 min (LRPC serial `150302F1DA270001`) ### Irrigation valve start/stop (form-encoded — confirmed via mitmproxy) ``` POST /module/sendManualModuleCommand Content-Type: application/x-www-form-urlencoded ``` Confirmed payload patterns: | Intent | Params | |---|---| | Run station N minutes | `commandType=station&command={outputId}&commandParams[hours]=0&commandParams[minutes]=N` | | Cycling station (on/off) | `commandType=station&command={outputId}&commandParams[hours]=1&commandParams[minutes]=0&commandParams[on]=1200&commandParams[off]=930&agriculturalModuleId={moduleId}` | | Stop station | `commandType=station&command={outputId}&commandParams=stop&agriculturalModuleId={moduleId}` | | All stations N minutes | `commandType=station&command=allStations&commandParams[hours]=0&commandParams[minutes]=N` | | Stop everything | `commandType=manualStop` | | Enable (no rain delay) | `commandType=status&command=on&commandParams=0` | | Disable | `commandType=status&command=off&commandParams=0` | | Rain delay N days | `commandType=status&command=off&commandParams=N` | | Max rain delay | `commandType=status&command=off&commandParams=255` | | Run program | `commandType=program&command={programId}` | Notes: - `command` accepts an **output ID string**, `"allStations"`, or a **program ID** (not an action name). - `agriculturalModuleId` is required for stop and cycling station commands. - `commandType=manualStop` takes no `outputIndex` or `commandParams`. ### Flush commands to device ``` POST /canopy/{siteId}/sendModulesCommand {} ``` ### Module LoRa data push (forces sync) ``` POST /modules/sendDataViaLoRaWAN POST /agriculturalModuleGroup/sendDataViaLoRaWAN POST /wateringModuleGroup/sendDataViaLoRaWAN ``` --- ## Program / schedule changes (persistent, survives reboot) ``` PUT /programs { "programs": [...], "module": "moduleId" } POST /programs create new program DELETE /programs delete program PATCH /programs/name rename POST /program/update socket.io variant (immediate push to device) PUT /program/update REST variant POST /program/reportProgramsDataSent acknowledge sync POST /checkAvailableWindows check time conflicts before saving ``` Program window format for `PUT /programs`: ```json { "programs": [{ "id": "...", "module": "moduleId", "index": 0, "weekDays": 127, "waterBudget": 100, "windows": [{ "start": 300, "end": 360, "on": 3600, "off": 0, "runningDays": 127 }], "programCharacteristics": { "programType": 4, "mode": 2, "onSpeed": 1 } }], "module": "moduleId" } ``` --- ## Module configuration (persistent device settings) ``` POST /remote/module/configuration socket.io: update device config POST /remote/module/configuration/and/programs config + programs in one push POST /input/update update input settings (name, thresholds) PUT /output/update update output settings (name, flowrate) POST /remote/module/name rename via LoRa PUT /module/update update module metadata PUT /module/update/address PUT /module/update/status POST /module/update/name POST /module/update/securityMode POST /module/reportModuleDataSent acknowledge device sync POST /remote/module/advertisingProfile BLE advertising config POST /remote/modules/advertisingProfile bulk POST /remote/module/dissociate { relayMsn, modulesMsn } — removes pairing PUT /module/dissociate/{moduleId} ``` --- ## Site / pool management ``` POST /canopy-sites create new site PUT /pools/update/address update pool address POST /pools/admin/sendMessage send admin message to pool customer PUT /teams/handleAccessGrantedPools grant pool access to team POST /teams/members/add POST /teams/members/create POST /agency/team/members/add POST /addAgriculturalGroupToUser POST /wateringModuleGroup/create PUT /wateringModuleGroup/update PUT /wateringModuleGroup/update/name PUT /wateringModuleGroup/updateFlowRate DELETE /wateringModuleGroup/delete POST /wateringModuleGroup/remove POST /updateAgriculturalModuleGroup ``` --- ## Journal / notifications ``` PUT /journal/acknowledge/all PUT /journal/acknowledge/records POST /canopy-sites/{siteId}/notifications/acknowledge-all POST /journal/notifications (write variant — mark read?) POST /journal/counts POST /journal/warnings/accepted ``` --- ## Content / documents (safe-ish) ``` PUT /favorites PUT /favorites/ POST /module/tags { module, tags: [...] } DELETE /document/remove DELETE /canopy/v2/deleteImage POST /hpaform/create POST /library/program-template POST /library/programming-template POST /notices/remove POST /polytropic/setIpxData POST /updateOutputIpxData ``` --- ## Admin-only (requires admin role — will 403 on standard account) ``` POST /admin/modules/search DELETE /admin/sim-card/delete POST /admin/sim-card/update POST /manufacturer/upload-blacklist POST /firmwares/removeModuleFirmware POST /pools/admin/sendMessage POST /pools/admin/search ``` --- ## How to discover more (without executing) Three complementary techniques, all read-only: **1. Static JS analysis** (already done above): ```bash # All $.ajax write calls grep -oE 'type:"(POST|PUT|PATCH|DELETE)"[^}]{0,200}url:"[^"]*"' /tmp/indygo_prod.js # All fetch() write calls grep -oE 'fetch\("[^"]*"[^)]{0,200}method:"(POST|PUT|PATCH|DELETE)"' /tmp/indygo_prod.js # Socket.io write calls grep -oE 'io\.socket\.(post|put|patch|delete)\("[^"]*"' /tmp/indygo_prod.js ``` **2. Safari Web Inspector recording** — record a session where you perform the action in the real app, then parse `inputs/*.json`: ```python import json with open("inputs/lrpc_post.json") as f: data = json.load(f) records = data["recording"]["records"] for rec in records: req = rec.get("entry", {}).get("request", {}) if req.get("method") in ("POST","PUT","PATCH","DELETE"): print(req["method"], req["url"]) print(" ", req.get("postData", {}).get("text", "")[:200]) ``` **3. Mitmproxy / Charles** (more complete than Safari — captures socket.io frames): ```bash mitmproxy --mode transparent --ssl-insecure -s dump_writes.py # dump_writes.py: filter on method != GET, log url + body ``` Safari doesn't capture WebSocket frames (socket.io messages), so LoRa push commands that go via socket.io are invisible in recordings. --- ## Socket.io write calls (discovered via bundle grep) These bypass the normal HTTP layer — standard Safari recording captures neither the URL nor body: ``` socket.post /checkAvailableWindows socket.post /input/update socket.post /program/update socket.post /remote/module/configuration socket.post /remote/module/configuration/and/programs socket.post /remote/module/name socket.put /module/update socket.put /module/update/address socket.put /pools/update/address socket.put /program/update ```