This document consolidates all reverse-engineered findings for the Moen Flo NAB (Smart Sump Pump Monitor) API. It is the single source of truth — API_ENDPOINTS_REFERENCE.md is superseded by this file.
- Overview
- Authentication
- Lambda Invoker Pattern
- Device Identification: Dual ID System
- Endpoints
- Device List
- Device Get (Single)
- Shadow Get (REST)
- Shadow Update (REST)
- Environment (Temp/Humidity)
- Usage History Top-10
- Last Usage and Estimated Next Run
- Pump Cycle Session History
- Event Logs
- Alert Settings by Device
- Get Current Alerts V2
- Get Active Alerts V2
- Get Current Alerts V1
- Acknowledge Alert
- Silence Alert
- MQTT Connection
- Data Reference
- Known Limitations
- Confirmed Non-Existent Endpoints
- Endpoint Naming Patterns
The Moen Flo NAB uses a serverless AWS architecture. All REST API calls are dispatched through a single Lambda invoker endpoint. Real-time device control requires MQTT over AWS IoT.
Key infrastructure:
- Auth: AWS Cognito User Pool (custom token endpoint)
- API: API Gateway → Lambda invoker → individual Lambda functions
- Real-time: AWS IoT MQTT (requires Cognito Identity Pool credentials)
POST https://4j1gkf0vji.execute-api.us-east-2.amazonaws.com/prod/v1/oauth2/token
User-Agent: Smartwater-iOS-prod-3.39.0
Content-Type: application/json
{
"client_id": "6qn9pep31dglq6ed4fvlq6rp5t",
"username": "user@example.com",
"password": "password"
}{
"token": {
"access_token": "eyJ...",
"id_token": "eyJ...",
"refresh_token": "eyJ...",
"expires_in": 3600
}
}- Use
access_tokeninAuthorization: Bearerheaders for Lambda invoker calls - Use
id_tokenfor AWS Cognito Identity Pool credential exchange (MQTT) - Tokens expire after 1 hour (3600 seconds)
- No client secret required (public client)
All REST API calls POST to a single invoker endpoint that dispatches to specific Lambda functions.
POST https://exo9f857n8.execute-api.us-east-2.amazonaws.com/prod/v1/invoker
Authorization: Bearer {access_token}
User-Agent: Smartwater-iOS-prod-3.39.0
Content-Type: application/json
{
"fn": "lambda-function-name",
"parse": false,
"escape": false,
"body": {
"key": "value"
}
}parse: Whentrue, the invoker JSON-parses the Lambda response body for youescape: Whentrue, enables JSON escaping of the body
{
"StatusCode": 200,
"Payload": {
"statusCode": 200,
"body": { ... }
}
}Response parsing pattern:
data = await resp.json()
if data.get("StatusCode") == 200:
payload = data.get("Payload")
if isinstance(payload, str):
payload = json.loads(payload) # First parse: sometimes double-encoded
if isinstance(payload, dict) and "body" in payload:
body = payload["body"]
if isinstance(body, str):
body = json.loads(body) # Second parse: body may be a JSON string
# body is now the actual dataNAB devices have two distinct identifiers. Using the wrong one returns null/404.
| Identifier | Format | Used For |
|---|---|---|
duid |
UUID string (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) |
Device list, event logs, alert endpoints |
clientId |
Integer (e.g., 123456789) |
Shadow API, environment, usage history |
Both IDs are returned by the device list endpoint:
devices = invoke_lambda("smartwater-app-device-api-prod-list", {"locale": "en_US"})
device = [d for d in devices if d.get("deviceType") == "NAB"][0]
duid = device["duid"] # UUID
client_id = device["clientId"] # Integer (pass as string to some endpoints)Alert endpoints use the numeric clientId (NOT UUID), passed as a string in pathParameters.duid. This naming is confusing but confirmed by testing.
Function: smartwater-app-device-api-prod-list
Purpose: List all devices on the account. Primary source for both device IDs.
Payload:
{"locale": "en_US"}Response: Array of device objects (see Device Response Fields)
Function: smartwater-app-device-api-prod-get
Purpose: Get a single device by clientId.
ID Type: Numeric clientId
Payload:
{"clientId": 123456789}Response: Same flat structure as a single item from device list. Does NOT wrap the device in a device.state envelope. Returns no additional fields beyond what the list endpoint provides.
Function: smartwater-app-shadow-api-prod-get
Purpose: Get the AWS IoT device shadow (cached device state).
ID Type: Numeric clientId
Payload:
{"clientId": 123456789}Response:
{
"state": {
"reported": {
"sens_on": true,
"droplet": {
"level": 14.2,
"trend": -0.5,
"floodRisk": 15,
"primaryState": "normal",
"backupState": "not_running"
},
"alerts": {
"266": {
"state": "active_unlack_unrack_unsuppressed",
"timestamp": 1736640106624
}
}
},
"desired": {}
}
}Notes:
- Returns cached state; device is NOT contacted
sens_oncontrols whether distance sensor is active (must betruefor readings)- To trigger a live reading, MQTT is required (see MQTT Connection)
Function: smartwater-app-shadow-api-prod-update
Purpose: Update the device shadow desired/reported state.
ID Type: Numeric clientId
Payload:
{
"clientId": 123456789,
"state": {
"reported": {
"sens_on": true
}
}
}Notes:
- REST shadow updates do NOT trigger device actions (device doesn't poll REST)
- For device commands (e.g., activate sensor), use MQTT
Function: fbgpg_usage_v1_get_device_environment_latest_prod
Purpose: Get current temperature and humidity readings.
ID Type: Numeric clientId (as string)
Payload:
{
"pathParameters": {
"clientId": "123456789"
}
}Response:
{
"temperature": 64.0,
"humidity": 48.0,
"ts": 1736640106624
}- Temperature: Fahrenheit
- Humidity: Percentage (0–100)
- Timestamp: Unix milliseconds
Function: fbgpg_usage_v1_get_usage_device_history_top10_prod
Purpose: Daily pump usage statistics (capacity, cycle counts).
ID Type: Numeric clientId
Payload:
{
"clientId": 123456789,
"queryStringParameters": {
"sortBy": "ts",
"orderBy": "desc",
"limit": "10"
}
}Response:
{
"data": [
{
"ts": 1736640106624,
"pumpCapacityPercentage": 23.5,
"pumpCycles": 15,
"date": "2024-01-15"
}
]
}Function: fbgpg_usage_v1_get_last_usage_prod
Purpose: Returns the most recent cycle's metadata and the backend's real-time prediction for when the next pump cycle will occur.
ID Type: Numeric clientId (passed as duid, as a string)
Payload:
{
"cognitoIdentityId": "",
"duid": "123456789",
"locale": "en_US"
}Response:
{
"duid": "123456789",
"lastOutgoTime": "2026-03-26T20:08:31.847Z",
"lastOutgoTimeMS": 6041000,
"lastOutgoVolume": 81078432,
"lastIncomeTimeMS": 6041000,
"lastIncomeVolume": 181162,
"backupUsed": false,
"estimatedNextRun": "2026-03-26T23:37:30.847Z",
"estimatedNextRunMS": 6498000,
"estimatedTimeUntilNextRunMS": 6446490
}Field notes:
| Field | Description |
|---|---|
lastOutgoTime |
ISO timestamp of when the last pump-out cycle was processed by the backend |
lastOutgoTimeMS |
Duration of the last pump-out cycle in milliseconds |
lastIncomeTimeMS |
Duration of last inflow (fill) period in milliseconds |
estimatedNextRun |
Static ISO timestamp — updated only when the backend batch-processes a completed session. Can be 1–3+ hours stale. |
estimatedNextRunMS |
Estimated time until next run in ms, anchored to lastOutgoTime (stable, not real-time) |
estimatedTimeUntilNextRunMS |
Real-time countdown from a separate prediction engine. Always fresh — counts down each poll, goes negative when overdue. This is what the Moen app uses for display. |
The app's calculateNextRunEstimate() method (from APK decompile) uses estimatedTimeUntilNextRunMS as a real-time countdown with the following display logic:
estimatedNextRun == null or "-1" → "Not Available"
seconds < -3600 → "Pump may run depending on weather"
-3600 ≤ seconds < -60 → "Within an hour"
-60 ≤ seconds < 0 → "Soon"
0 ≤ seconds < 60 → "In X seconds"
60 ≤ seconds < 3600 → "In X minutes"
3600 ≤ seconds < 43200 → "In X hours" (integer floor)
seconds ≥ 43200 → "Pump may run depending on weather"
Key insight: The app does not compute a specific future timestamp for the "Within an hour" or "Soon" zones — it displays text labels. estimatedTimeUntilNextRunMS goes negative when the pump is overdue; the app treats values between -3600s and 0 as "still expected soon."
HA integration approach: The integration uses now + estimatedTimeUntilNextRunMS directly as a TIMESTAMP sensor. When positive, this shows a future time ("in X minutes/hours"). When slightly negative, HA shows "X minutes ago", which conveys the pump is overdue. The sensor returns unavailable only when estimatedNextRun is null or "-1" (backend has no estimate at all).
Why estimatedNextRun is not used: It is a static batch-computed timestamp that does not update until the backend finishes processing the next session. It can be several hours stale. Confirmed via tests/test_estimated_next_cycle.py.
Why drop_on does not help: Sending crockCommand: drop_on before calling this endpoint does not cause estimatedNextRun or lastOutgoTime to refresh within 60 seconds. The backend updates these fields only after a full pump cycle is detected and processed. Confirmed via tests/test_drop_on_timing.py (archived).
Function: fbgpg_usage_v1_get_my_usage_device_history_prod
Purpose: Detailed per-cycle data including fill/empty volumes and durations.
ID Type: Numeric clientId (passed as duid)
Critical: Must include "type": "session" or you get daily aggregates, not per-cycle data.
Payload:
{
"cognitoIdentityId": "us-east-2:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"duid": 123456789,
"type": "session",
"limit": 10,
"locale": "en_US"
}Response:
{
"usage": [
{
"date": "2024-12-29T10:30:15.000Z",
"fillVolume": 1.2,
"fillVolumeUnits": "gpm",
"fillTimeMS": 45000,
"emptyVolume": 5.5,
"emptyVolumeUnits": "gal",
"emptyTimeMS": 12000,
"backupRan": false
}
]
}fillVolume— Water inflow rate (gallons per minute)fillTimeMS— Duration water was filling the basin (ms)emptyVolume— Volume pumped out per cycle (gallons)emptyTimeMS— How long pump ran (ms)backupRan— Whether backup pump engaged
Function: fbgpg_logs_v1_get_device_logs_user_prod
Purpose: Historical event log with notification IDs and titles.
ID Type: UUID duid
Payload:
{
"cognitoIdentityId": "us-east-2:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"duid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"limit": 200,
"locale": "en_US"
}Response:
{
"events": [
{
"id": "267",
"title": "Main Pump Stops Normally",
"text": "The main pump has stopped running.",
"time": 1736640106624,
"severity": "info"
}
]
}Note: This endpoint is the only source of notification title metadata. There is no dedicated /notification-types endpoint — titles must be mined from event logs.
Function: fbgpg_user_v1_alert_settings_by_device_get_prod
Purpose: Get per-alert notification channel preferences (push, email, voice, text).
ID Type: Numeric clientId (as string, in pathParameters.duid)
Payload:
{
"pathParameters": {
"duid": "123456789"
}
}Response: Array of alert setting objects:
[
{
"alertTypeId": 224,
"push": true,
"email": true,
"voice": false,
"text": false,
"args": ["223", "225"]
}
]- Returns ~20 alert type IDs per device
- The
argsfield on some alerts contains related alert IDs (e.g., alert 224 "High Water Level" has args referencing the low/normal threshold alert IDs) - Passing the UUID
duidreturns empty results; numericclientIdis required
Function: fbgpg_alerts_v2_get_alerts_current_by_user_prod
Purpose: Get only unacknowledged alerts. Recommended for HA integration.
Payload:
{}Response:
{
"alerts": [
{
"id": "266",
"duid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"time": "2026-01-12T00:21:46.624Z",
"state": "active_unlack_unrack_unsuppressed",
"severity": "warning",
"title": "Main Pump Not Stopping",
"text": "The main pump will not stop running...",
"detailsObject": {
"flags": ["allow_active_rem_ack", "notify", "push", "email"],
"priority": 80
},
"dismiss": true,
"silence": false
}
]
}dismiss: true→ Alert can be dismissed via the acknowledge endpointdismiss: false→ Alert cannot be manually dismissed (resolves when condition clears)- Acknowledged alerts are automatically excluded from this endpoint's results
Function: fbgpg_alerts_v2_get_alerts_active_by_user_prod
Purpose: Get ALL active alerts including acknowledged ones.
Payload: {}
Response: Same structure as V2 current endpoint.
Note: Do not use for display — includes acknowledged alerts that should be hidden from users.
Function: fbgpg_alerts_v1_get_alerts_current_by_user_prod
Purpose: V1 alert retrieval. Returns different structure; lacks dismiss/silence fields.
Payload: {}
Response includes: active, localAck, remoteAck, suppressed, actions fields (different schema from V2).
Function: fbgpg_alerts_v1_acknowledge_alert_prod
Purpose: Mark an alert as acknowledged (changes unlack → lack in shadow state).
ID Type: Numeric clientId AS STRING, in pathParameters.duid
Payload:
{
"pathParameters": {
"duid": "123456789",
"alertEventId": "266"
}
}Invoker format (use parse: true, escape: true):
{
"fn": "fbgpg_alerts_v1_acknowledge_alert_prod",
"parse": true,
"escape": true,
"body": {
"pathParameters": {
"duid": "123456789",
"alertEventId": "266"
}
}
}Response: HTTP 204 No Content
Effect:
- Alert state:
active_unlack_unrack_unsuppressed→active_lack_unrack_unsuppressed - Alert's
dismissfield changes fromtruetofalse - Alert is removed from
fbgpg_alerts_v2_get_alerts_current_by_user_prodresults - Alert remains visible in device shadow and Moen app (does NOT fully dismiss)
Function: fbgpg_alerts_v1_silence_alert_prod
Purpose: Silence/suppress an alert. Functionally identical to acknowledge.
Payload: Same as acknowledge alert.
Response: HTTP 204 No Content
Effect: Identical to fbgpg_alerts_v1_acknowledge_alert_prod.
Real-time device reading triggers (e.g., activating sens_on) require MQTT over AWS IoT. REST shadow updates do not command the device.
- Exchange Cognito tokens for Identity credentials:
import boto3
cognito_client = boto3.client("cognito-identity", region_name="us-east-2")
identity_pool_id = "us-east-2:7880fbef-a3a8-4ffc-a0d1-74e686e79c80"
user_pool_provider = "cognito-idp.us-east-2.amazonaws.com/us-east-2_Cqk5OcQJh"
# Get identity ID
identity_resp = cognito_client.get_id(
IdentityPoolId=identity_pool_id,
Logins={user_pool_provider: id_token}
)
identity_id = identity_resp["IdentityId"]
# Get credentials
creds_resp = cognito_client.get_credentials_for_identity(
IdentityId=identity_id,
Logins={user_pool_provider: id_token}
)
credentials = creds_resp["Credentials"]
# credentials["AccessKeyId"], credentials["SecretKey"], credentials["SessionToken"]- Connect via MQTT:
import aiomqtt
MQTT_ENDPOINT = "a1p3vgpxlnkaa2-ats.iot.us-east-2.amazonaws.com"
MQTT_PORT = 443
SHADOW_TOPIC = f"$aws/things/{client_id}/shadow/update"
async with aiomqtt.Client(
hostname=MQTT_ENDPOINT,
port=MQTT_PORT,
transport="websockets",
websocket_path="/mqtt",
# ... SigV4 auth headers using credentials
) as client:
await client.publish(
SHADOW_TOPIC,
json.dumps({"state": {"desired": {"sens_on": True}}})
)$aws/things/{clientId}/shadow/update # Publish desired state
$aws/things/{clientId}/shadow/update/accepted # Subscribe for confirmation
$aws/things/{clientId}/shadow/get # Request current shadow
$aws/things/{clientId}/shadow/get/accepted # Subscribe for shadow response
Fields returned by smartwater-app-device-api-prod-list and smartwater-app-device-api-prod-get:
| Field | Type | Description |
|---|---|---|
duid |
string | UUID device identifier |
clientId |
integer | Numeric device identifier |
nickname |
string | User-assigned device name |
location |
string | Location description |
deviceType |
string | "NAB" for sump pump monitors |
connected |
boolean | Device online status |
batteryPercentage |
integer | Battery charge (0–100) |
powerSource |
string | Power source description |
wifiRssi |
integer | WiFi signal strength (dBm, negative) |
fwVersion |
string | Firmware version string |
crockTofDistance |
float | Distance from sensor to water surface (inches). Lower = higher water level. |
crockDiameter |
float | Sump pit diameter (inches) |
droplet |
object | Computed flood risk state (see below) |
federatedIdentity |
string | Cognito Identity ID (used for MQTT auth and some Lambda calls) |
lastHeardFromTime |
integer | Timestamp of last device communication (Unix ms) |
alerts |
array | Active alert summaries |
Fields that do NOT exist (do not use these):
— Does not exist in any API endpointwaterLevelCritical— Does not exist in any API endpointwaterLevelWarning— UseisConnectedconnected— UsebatteryLevelbatteryPercentage— UseisOnBatterypowerSource— UsesignalStrengthwifiRssi
The droplet object appears in both the device list and device shadow. It represents the Moen backend's computed flood risk assessment:
{
"level": 14.2,
"trend": -0.5,
"floodRisk": 15,
"primaryState": "normal",
"backupState": "not_running"
}| Field | Type | Description |
|---|---|---|
level |
float | Current water level (inches from floor, not sensor) |
trend |
float | Rate of change (inches/hour, negative = falling) |
floodRisk |
integer | Risk score (0–100) |
primaryState |
string | Main pump state ("normal", "running", etc.) |
backupState |
string | Backup pump state ("not_running", "running", etc.) |
Water level thresholds (critical/warning) are computed server-side and published only as part of floodRisk. They are not configurable via API and not exposed as raw values.
Alert states follow the pattern: {activity}_{ack}_{rack}_{suppressed}
| Component | Values | Meaning |
|---|---|---|
| activity | active / inactive |
Whether the condition is currently occurring |
| ack | unlack / lack |
Whether user has acknowledged (lack = acknowledged) |
| rack | unrack / rack |
Unknown purpose (possibly "re-acknowledged") |
| suppressed | unsuppressed / suppressed |
Whether alert is suppressed/muted |
Examples:
active_unlack_unrack_unsuppressed— New unacknowledged active alert (shown in app)active_lack_unrack_unsuppressed— Active but acknowledged (API acknowledge result)inactive_unlack_unrack_unsuppressed— Condition resolved, not yet acknowledged
Alert IDs from three sources:
fbgpg_user_v1_alert_settings_by_device_get_prod— Comprehensive list of IDs configured on devicefbgpg_logs_v1_get_device_logs_user_prod— Titles and severities from event history- Decompiled Android APK — Enum names, dismiss behavior, and shadow commands
Dismiss behavior:
dismiss: true— Can be acknowledged viafbgpg_alerts_v1_acknowledge_alert_proddismiss: false— Cannot be dismissed via normal flow; resolves when condition clears or via shadow command
| ID | Title | Severity | dismiss | Notes | Source |
|---|---|---|---|---|---|
| 213 | (unknown) | — | — | Alert settings | |
| 218 | Backup Test Scheduled | info | false | Pathway notification; not shown in app when actionable alerts present; excluded from HA active count | Event logs |
| 222 | (unknown) | — | — | Alert settings | |
| 224 | High Water Level | warning | true | Event logs + alert settings + active alerts API | |
| 225 | Normal Water Level | info | — | Event logs | |
| 230 | (unknown) | — | — | Alert settings | |
| 232 | Overflow Water Level | critical | true | "Water in the sump basin is above overflow water level. Take immediate action." ack_on_clear: false. Confirmed during high-water event. |
Active alerts API |
| 236 | Sensor Too Close | critical | — | Event logs | |
| 250 | (unknown) | — | — | Alert settings | |
| 254 | Critical Flood Risk | critical | true | "There is an imminent flood risk due to water level and equipment failure." ack_on_clear: true. Confirmed during high-water event. |
Event logs + alert settings + active alerts API |
| 256 | High Flood Risk | critical | — | Event logs + alert settings | |
| 258 | Flood Risk | warning | true | ack_on_clear: true. Confirmed during high-water event. |
Event logs + alert settings + active alerts API |
| 259 | Flood Risk Cleared | info | — | Event logs | |
| 260 | Main Pump Failed | critical | true | ack_on_clear: true. Confirmed during high-water event. |
Event logs + alert settings + active alerts API |
| 261 | Main Pump Reset | info | — | Event logs | |
| 262 | Main Pump Overwhelmed | critical | true | ack_on_clear: false. Confirmed during high-water event. |
Event logs + alert settings + active alerts API |
| 263 | Main Pump Recovered | info | — | Event logs | |
| 266 | Main Pump Not Stopping | warning | false | Pathway 2 alert; cleared by shadow command crockCommand: rst_primary (from RESET_PRIMARY_STATE enum in APK). Cannot be dismissed via normal acknowledge flow. |
Event logs + alert settings + APK |
| 267 | Main Pump Stops Normally | info | — | Event logs | |
| 268 | Backup Pump Failed | critical | true | "The backup pump does not seem to run normally." Confirmed via v2 API live data. | Active alerts API |
| 269 | Backup Pump Reset | info | — | Event logs | |
| 270 | (unknown) | — | — | Previously inferred as "Backup Pump Stops Normally" but 268 is confirmed as "Backup Pump Failed" (not a state-change pair with 266/267); inference withdrawn | Alert settings |
| 301 | (unknown) | — | — | Seen in alert settings args on North pump | Alert settings |
| 1716 | (unknown) | — | — | Alert settings | |
| 1718 | (unknown) | — | — | Alert settings | |
| 1720 | (unknown) | — | — | Alert settings | |
| 1722 | (unknown) | — | — | Alert settings | |
| 2802 | (unknown) | — | — | Alert settings | |
| 2803 | (unknown) | — | — | Alert settings |
Notification types seen in the Moen app UI, not yet mapped to IDs:
- Dead Battery (critical)
- Backup Test Failed (critical)
- Water Detected (critical)
- Backup Pump Overwhelmed (warning)
- Device Lost (warning)
- Water Level Sensor Communication Lost (warning)
- Main Power Lost (warning)
- Low Battery (warning)
- Possible Drain Backflow (warning)
- Main Power Restored (info)
- Check Battery Water Level (info)
- Network Connected (info)
-
No water level threshold API:
waterLevelCriticalandwaterLevelWarningdo not exist in any API endpoint. They were incorrectly documented in an earlier version of this file. Water level risk is expressed only through thedroplet.floodRiskscore (0–100), computed server-side. -
No dedicated notification metadata endpoint: No
/notification-typesor equivalent Lambda function exists. Notification titles must be mined from event logs. SeeNOTIFICATION_DISCOVERY.md. -
Alert dismissal is partial: API acknowledge/silence (
lack) does not fully dismiss alerts. Manual dismissal in the Moen app removes alerts from shadow entirely; API acknowledge only changes theunlack→lackcomponent while the alert remains in shadow. -
No historical water level data: Only current sensor distance (
crockTofDistance) is available. The API does not store or return historical water level readings. -
REST shadow updates don't command device: To trigger a live sensor reading (
sens_on), MQTT is required. REST shadow writes are ignored by the device. -
fbgpg_alerts_v1_get_alert_types_proddoes not exist: Confirmed LambdaResourceNotFoundException. -
type: "session"required for per-cycle data: Without this parameter,fbgpg_usage_v1_get_my_usage_device_history_prodreturns daily aggregates instead of per-cycle records.
These Lambda function names were tested and returned ResourceNotFoundException or equivalent errors:
fbgpg_alerts_v1_get_alert_types_prodsmartwater-app-location-api-prod-listsmartwater-app-house-api-prod-listfbgpg_location_v1_get_locations_prodfbgpg_house_v1_get_houses_prodsmartwater-app-user-api-prod-locationssmartwater-app-user-api-prod-houses
Two generations of naming exist:
| Pattern | Example | Era |
|---|---|---|
smartwater-app-{service}-api-prod-{action} |
smartwater-app-device-api-prod-list |
Legacy |
fbgpg_{service}_v{ver}_{action}_prod |
fbgpg_usage_v1_get_my_usage_device_history_prod |
Current |
Service codes (fbgpg):
alerts— Alert management and retrievaldevice— Device control and attributesusage— Statistics and session historylogs— Event logginguser— User settings and preferences
- Decompiled Android APK (jadx) — Primary source for Lambda function names and payload structures. Key files:
com/moen/smartwater/base/utils/ConstantsKt.java— All endpoint name constantscom/moen/smartwater/base/data/repositories/AlertRepository.java— Alert payload structures
- HTTPS traffic interception (mitmproxy with SSL pinning bypass via Frida)
- Exhaustive endpoint testing — Permuting known patterns against all discovered function names
This documentation is provided for educational and Home Assistant integration purposes only. The Moen Flo API is not officially public. This integration is not endorsed by Moen or Fortune Brands. Use at your own risk.