REST API for accessing customer, device, and alert data within your hierarchy
This implementation differs from the original API Specification (private):
| Aspect | Specification | Implementation |
|---|---|---|
| URL prefix | /api/v1/ |
/customer/external/v1/ (customers, contacts)/product/external/v1/ (devices, alerts) |
| Authentication | Authorization: Bearer {key} |
Authorization: Api-Key {key} |
All API requests require an API Key in the Authorization header:
Authorization: Api-Key <your-api-key>
Internal: How to create API keys (private)
| Method | Endpoint | Description |
|---|---|---|
| GET | /customer/external/v1/customers/ | List customers |
| GET | /customer/external/v1/customers/{id}/ | Get customer details |
| GET | /customer/external/v1/contacts/ | List contacts |
| GET | /customer/external/v1/contacts/{id}/ | Get contact details |
| Method | Endpoint | Description |
|---|---|---|
| GET | /product/external/v1/devices/ | List devices |
| GET | /product/external/v1/devices/{id}/ | Get device details |
| GET | /product/external/v1/devices/{id}/cartridges/ | Get device cartridges |
| GET | /product/external/v1/alerts/ | List alerts |
| GET | /product/external/v1/alerts/{id}/ | Get alert details |
| Parameter | Example | Description |
|---|---|---|
customer_type | ?customer_type=DEALER | Filter by type |
is_active | ?is_active=true | Filter by status |
associated_with | ?associated_with=123 | Filter by parent ID |
| Parameter | Example | Description |
|---|---|---|
customer_id | ?customer_id=123 | Filter by customer |
is_active | ?is_active=true | Filter by status |
| Parameter | Example | Description |
|---|---|---|
customer_id | ?customer_id=123 | Filter by customer |
is_installed | ?is_installed=true | Filter by installation status |
| Parameter | Example | Description |
|---|---|---|
device_id | ?device_id=123 | Filter by device |
customer_id | ?customer_id=123 | Filter by customer |
date | ?date=2024-01-15 | Filter by specific date |
date_from / date_to | ?date_from=2024-01-01&date_to=2024-01-31 | Filter by date range |
alert_type | ?alert_type=INPUT_TDS | Filter by alert type |
| Type | Description |
|---|---|
INPUT_TDS | Input water TDS exceeded threshold |
OUTPUT_TDS | Output water TDS exceeded threshold |
TDS_VARIABILITY | TDS variability threshold exceeded |
TEMPERATURE_HIGH | High temperature alert |
TEMPERATURE_LOW | Low temperature alert |
FLOW_RATE | Flow rate below threshold |
RO_REJECTION | RO rejection ratio below threshold |
DAILY_VOLUME | Daily volume threshold exceeded |
TOTAL_VOLUME | Total volume threshold exceeded |
CARTRIDGE_MANUFACTURER | Manufacturer suggested replacement |
CARTRIDGE_CALCULATED | Calculated replacement needed |
WIFI_CONNECTION | WiFi connectivity issue |
Instead of polling the alerts endpoint, you can receive alerts as push
notifications. When an alert triggers for one of your devices, WaterTDS sends an HTTP
POST to an HTTPS endpoint you configure. It is the same set of alert events
you get by email — the event toggles decide what is sent, the webhook
switch decides whether it is also pushed to your endpoint.
Set up in the WaterTDS B2B admin (Company → Notifications):
POST <your endpoint>
Content-Type: application/json
Authorization: Bearer <your secret>
X-WTDS-Event-Id: <uuid> # unique per event; use for idempotency
Authorization header equals
Bearer <your secret> (constant-time compare) before processing; reject
others with 401.
{
"event_types": ["daily_thresholds_alerts"],
"event_id": "63679b4d-1ac3-426d-8d9e-e3f95888998e",
"company_id": 94,
"device_id": 315,
"timestamp": "2026-06-29T14:35:49.825388+00:00",
"data": {
"device_id": 315,
"user": "Acme Water Co",
"label": "Kitchen RO",
"model": "RO-500",
"manufacturer": "Acme",
"install_date": "2022-11-02",
"address_1": "...", "city": "...", "state": "...", "zip_code": "...",
"alert_messages": [
"The TDS level of the output is greater than 51 ppm. You had set up this alert."
]
}
}
| Field | Type | Description |
|---|---|---|
event_types | string[] | Which alert types this delivery covers |
event_id | string (uuid) | Idempotency key (also in X-WTDS-Event-Id) |
company_id | int | Your WaterTDS company id |
device_id | int | Device that triggered the alert |
timestamp | string (ISO-8601, UTC) | When the alert was generated |
data.alert_messages | string[] | Human-readable alert message(s) |
event_types values: monthly_quality_report,
calculation_based_alerts, manufacturer_suggested_alerts,
daily_thresholds_alerts, hourly_thresholds_alerts.
event_id —
if already processed, return 2xx and ignore.
import hmac
from flask import Flask, request, abort
SECRET = "your-secret-from-the-admin"
seen = set()
app = Flask(__name__)
@app.post("/wtds-webhook")
def hook():
if not hmac.compare_digest(request.headers.get("Authorization", ""), f"Bearer {SECRET}"):
abort(401)
event = request.get_json()
if event["event_id"] in seen: # idempotency
return "", 200
seen.add(event["event_id"])
for msg in event["data"].get("alert_messages", []):
print(event["device_id"], event["event_types"], msg)
return "", 200
All list endpoints support pagination:
| Parameter | Default | Description |
|---|---|---|
page | 1 | Page number |
page_size | 10 | Items per page (max: 100) |
Response format:
{
"count": 50,
"next": "https://api.watertds.com/.../devices/?page=2",
"previous": null,
"results": [...]
}
curl -X GET "https://api.watertds.com/customer/external/v1/customers/" \
-H "Authorization: Api-Key <your-api-key>"
curl -X GET "https://api.watertds.com/product/external/v1/devices/?customer_id=123" \
-H "Authorization: Api-Key <your-api-key>"
curl -X GET "https://api.watertds.com/product/external/v1/alerts/?date=2024-01-15" \
-H "Authorization: Api-Key <your-api-key>"
Your API key is linked to a customer account. You can only access: