💧 aioflo: a Python3, asyncio-friendly library for Flo Smart Water Detectors
aioflo is a Python 3, asyncio-friendly library for interacting with
Flo by Moen Smart Water Detectors.
Installation
pip install aioflo
Usage
import asyncio
from datetime import datetime
from aioflo import async_get_api
async def main() -> None:
"""Run!"""
api = await async_get_api("<EMAIL>", "<PASSWORD>")
# Get user account information:
user_info = await api.user.get_info()
a_location_id = user_info["locations"][0]["id"]
# Get location (i.e., device) information:
location_info = await api.location.get_info(a_location_id)
# Get device information
first_device = location_info["devices"][0]
first_device_id = first_device["id"]
device_info = await api.device.get_info(first_device_id)
# Get all alarms:
alarms = await api.alarm.get_all()
# Run a health test
health_test_response = await api.device.run_health_test(first_device_id)
# Close the shutoff valve
close_valve_response = await api.device.close_valve(first_device_id)
# Open the shutoff valve
open_valve_response = await api.device.open_valve(first_device_id)
# Get consumption info between a start and end datetime (location-wide aggregate):
consumption_info = await api.water.get_consumption_info(
a_location_id,
datetime(2020, 1, 16, 0, 0),
datetime(2020, 1, 16, 23, 59, 59, 999000),
)
# Scope consumption to a single device. Pass device_mac_address when a location
# has multiple Flo devices; omit it for the location-wide total:
device_consumption = await api.water.get_consumption_info(
a_location_id,
datetime(2020, 1, 16, 0, 0),
datetime(2020, 1, 16, 23, 59, 59, 999000),
device_mac_address=first_device["macAddress"],
)
# Get various other metrics related to water usage:
metrics = await api.water.get_metrics(
first_device["macAddress"],
datetime(2020, 1, 16, 0, 0),
datetime(2020, 1, 16, 23, 59, 59, 999000),
)
# Get recent Flo Detect water-flow events (near-real-time usage):
events = await api.flodetect.get_events(
first_device["macAddress"],
to=datetime(2026, 7, 12, 10, 25, 4),
limit=20,
)
# The payload groups events per device under items[].events. Flatten it:
flow_events = api.flodetect.parse_events(events)
# Each event has startAt, endAt, totalGal, duration, and predicted.displayText.
# Set the device in "Away" mode:
await api.location.set_mode_away(a_location_id)
# Set the device in "Home" mode:
await api.location.set_mode_home(a_location_id)
# Set the device in "Sleep" mode for 120 minutes, then return to "Away" mode:
await api.location.set_mode_sleep(a_location_id, 120, "away")
asyncio.run(main())
By default, the library creates a new connection to Flo with each coroutine. If you are
calling a large number of coroutines (or merely want to squeeze out every second of
runtime savings possible), an
aiohttp ClientSession can be used for connection
pooling:
import asyncio
from datetime import datetime
from aiohttp import ClientSession
from aioflo import async_get_api
async def main() -> None:
"""Create the aiohttp session and run the example."""
async with ClientSession() as session:
api = await async_get_api("<EMAIL>", "<PASSWORD>", session=session)
# Tell Flo to get updated data from the device
ping_response = await api.presence.ping()
# Get user account information:
user_info = await api.user.get_info()
a_location_id = user_info["locations"][0]["id"]
# Get location (i.e., device) information:
location_info = await api.location.get_info(a_location_id)
# Get device information
first_device = location_info["devices"][0]
first_device_id = first_device["id"]
device_info = await api.device.get_info(first_device_id)
# Get all alarms:
alarms = await api.alarm.get_all()
# Run a health test
health_test_response = await api.device.run_health_test(first_device_id)
# Close the shutoff valve
close_valve_response = await api.device.close_valve(first_device_id)
# Open the shutoff valve
open_valve_response = await api.device.open_valve(first_device_id)
# Get consumption info between a start and end datetime (location-wide aggregate):
consumption_info = await api.water.get_consumption_info(
a_location_id,
datetime(2020, 1, 16, 0, 0),
datetime(2020, 1, 16, 23, 59, 59, 999000),
)
# Scope consumption to a single device. Pass device_mac_address when a location
# has multiple Flo devices; omit it for the location-wide total:
device_consumption = await api.water.get_consumption_info(
a_location_id,
datetime(2020, 1, 16, 0, 0),
datetime(2020, 1, 16, 23, 59, 59, 999000),
device_mac_address=first_device["macAddress"],
)
# Get various other metrics related to water usage:
metrics = await api.water.get_metrics(
first_device["macAddress"],
datetime(2020, 1, 16, 0, 0),
datetime(2020, 1, 16, 23, 59, 59, 999000),
)
# Get recent Flo Detect water-flow events (near-real-time usage):
events = await api.flodetect.get_events(
first_device["macAddress"],
to=datetime(2026, 7, 12, 10, 25, 4),
limit=20,
)
# The payload groups events per device under items[].events. Flatten it:
flow_events = api.flodetect.parse_events(events)
# Each event has startAt, endAt, totalGal, duration, and predicted.displayText.
# Set the device in "Away" mode:
await api.location.set_mode_away(a_location_id)
# Set the device in "Home" mode:
await api.location.set_mode_home(a_location_id)
# Set the device in "Sleep" mode for 120 minutes, then return to "Away" mode:
await api.location.set_mode_sleep(a_location_id, 120, "away")
asyncio.run(main())
Moen SSO (Cognito) auth
The current Moen Smartwater app authenticates against Moen's SSO endpoint rather than the
legacy Flo users/auth flow. use_sso=True opts into it: the access token is sent to
api-gw.meetflo.com as a bearer token and is refreshed on expiry and on a 401, falling
back to a full login if the refresh token is rejected.
api = await async_get_api("<EMAIL>", "<PASSWORD>", use_sso=True)
The legacy flow is the default and is unchanged. The legacy endpoint still works, so this is cover for it being retired rather than a fix for a current failure.
Metadata
Release files for aioflo 2026.9.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| aioflo-2026.9.4.tar.gz | 12.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aioflo-2026.9.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 27.3 kB
Release files / aioflo-2026.9.4.tar.gz
| Download URL | aioflo-2026.9.4.tar.gz |
|---|---|
| Size | 12.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fe1b18114fcce556d2e16e741091d8ff0af2087aecc499da0c9156f15beca005
|
|
BLAKE2b-256 checksum How to use checksums |
1234127dc1f6d802a2122bd8ad650624fbc1dc71c079c0fc515ec2ed52ab57f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.5.1 CPython/3.13.15 Linux/6.17.0-1022-azure
|
Release files / aioflo-2026.9.4-py3-none-any.whl
| Download URL | aioflo-2026.9.4-py3-none-any.whl |
|---|---|
| Size | 15.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1953566ce315511e6b9d4a84e86c6f53550bbcb7b7c30cdf585641c6a51d7918
|
|
BLAKE2b-256 checksum How to use checksums |
cc339910125349d1f48b5c9797b308d51776b90282639bf87c91e511e866f9d1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.5.1 CPython/3.13.15 Linux/6.17.0-1022-azure
|
Security for aioflo 2026.9.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.