Skip to main content

External API Reference

Base path: /api/external · Swagger UI: /api/external/docs

Base URLs

EnvironmentBase URL
Productionhttps://prod-aquagen.azurewebsites.net
Localhttp://localhost:5000

Example:

https://prod-aquagen.azurewebsites.net/api/external/latest/unit?unitId=U001
https://prod-aquagen.azurewebsites.net/api/external/latest/all
https://prod-aquagen.azurewebsites.net/api/external/waterRatio/?startDate=01/11/2024&endDate=30/11/2024&type=daily
info

The External API provides a minimal, stable surface for third-party systems that need to read live device data or submit water ratio records without access to the full User or Admin API. Authentication is identical to the User API — the same JWT token issued by GET /api/user/user/login works here.


Authentication

External API endpoints use the same JWT issued by the User API login endpoint.

Authorization: Bearer <access_token>

Obtain a token via:

GET /api/user/user/login
Authorization: Bearer <azure_ad_token>
loginType: microsoft

(or any other loginType supported by the User API — see User API Reference → Authentication)

Token validation: Every request passes through auth_interceptor (@app.before_request), which calls TokenService.validate_token() against token_logs in Cosmos DB. A deactivated or expired token returns 440 before the route handler runs.

Swagger UI browsing only: GET /api/external/auth/swaggerLogin renders an HTML login form. Submit username and password — on success, an access_token_cookie is set (httponly, 1-hour max-age) and the browser is redirected to /api/external/docs. Not for programmatic use.


Device Data

GET /api/external/latest/unit

Returns the single most recent raw reading for a specific unit. This is a real-time endpoint — it always returns the device's latest transmitted value regardless of date.

GET /api/external/latest/unit?unitId=U001
Authorization: Bearer <access_token>
ParameterRequiredDescription
unitIdYesUnit to retrieve the latest reading for — 1403 if missing
How it works
  1. unitId is validated against current_user['unitsMapping'] — only units belonging to the authenticated user's industry are accessible
  2. ExternalUnitDataService(args).get_top_one_device_data_for_unit() queries Cosmos DB: SELECT TOP 1 * FROM c WHERE c.unitId = @unitId ORDER BY c._ts DESC
  3. ExternalUnitDataFormatter().format_device_data(current_user['unitsMapping'], items) applies display metadata:
    • Unit display name from unitsMapping
    • SI unit (e.g. KL, , m)
    • Raw value divided by divisionFactor (default 1000)
  4. If no data is found → 404

Response shape:

{
"data": {
"unitId": "U001",
"unitName": "Main Inlet",
"value": 1234.56,
"siUnit": "KL",
"timestamp": "2024-11-09T10:30:00Z"
},
"status": "Success"
}
StatusCause
200Latest reading returned
404No data found for the unit
401Missing or expired token
420JWT expired
440Session revoked — token deactivated in token_logs
1403unitId missing

GET /api/external/latest/all

Returns the latest reading for every unit belonging to the authenticated user's industry. No parameters required — the industry is inferred from the JWT.

GET /api/external/latest/all
Authorization: Bearer <access_token>

How it works:

  1. industryId is taken from current_user['industryId'] in the JWT
  2. ExternalIndustryUnitDataService(args).get_top_one_device_data_for_industry() queries Cosmos DB for the most recent reading for each unit in the industry
  3. All results are passed through ExternalUnitDataFormatter().format_device_data(current_user['unitsMapping'], items) — same formatting as /unit (display name, SI unit, division factor)
  4. If no data is found for any unit → 404

Response shape:

{
"data": [
{
"unitId": "U001",
"unitName": "Main Inlet",
"value": 1234.56,
"siUnit": "KL",
"timestamp": "2024-11-09T10:30:00Z"
},
{
"unitId": "U002",
"unitName": "Overhead Tank",
"value": 45.2,
"siUnit": "KL",
"timestamp": "2024-11-09T10:28:00Z"
}
],
"status": "Success"
}
note

Each unit's latest reading is fetched independently. Units that have never transmitted data are silently omitted from the array — only units with at least one reading appear in the response.

StatusCause
200Latest reading for each unit in the industry
404No data found for any unit
401Missing or expired token
420JWT expired
440Session revoked

Water Ratio Data

Base path: /api/external/waterRatio

Water ratio tracks the relationship between water consumed and water produced/supplied. Records are keyed by date + time — each combination must be unique. All three methods are JWT-protected and scoped to the authenticated user's industryId.

GET /api/external/waterRatio/

Retrieve water ratio records for a date range.

GET /api/external/waterRatio/?startDate=01/11/2024&endDate=30/11/2024&type=daily
Authorization: Bearer <access_token>
ParameterRequiredDefaultDescription
startDateYesDD/MM/YYYY — start of the range — 1403 if missing
endDateNoDD/MM/YYYY — end of range. Defaults to startDate if omitted (single-day fetch)
typeNodailydaily — day-level records; monthly — month-level aggregates
How it works
  1. industryId resolved from current_user['industryId']
  2. WaterRatioDataService(request_data).get_data() queries Cosmos DB for all water ratio documents within [startDate, endDate] for the industry
  3. For type=monthly, results are aggregated per month
  4. Returned as an array sorted by date ascending

Response shape:

[
{
"date": "01/11/2024",
"time": "08:00:00",
"industryId": "IND001",
"ratio": 0.85,
"produced": 10000,
"consumed": 8500
}
]
StatusCause
200Array of water ratio records
1403startDate missing

POST /api/external/waterRatio/

Submit a new water ratio data point. The date + time combination must be unique per industry — attempting to insert a duplicate returns 1403.

POST /api/external/waterRatio/?date=09/11/2024&time=08:00:00
Authorization: Bearer <access_token>
Content-Type: application/json

{
"ratio": 0.85,
"produced": 10000,
"consumed": 8500
}
ParameterRequiredDescription
dateYesDD/MM/YYYY — record date — 1403 if missing
timeYesHH:MM:SS — record time — 1403 if missing

Body: JSON object with the ratio data fields to store. Shape is industry-specific.

How it works
  1. industryId resolved from current_user['industryId']
  2. WaterRatioDataService(request_data).save_data() checks if a document already exists for (industryId, date, time)
  3. If a record already exists → returns None → route returns 1403 "Already data exists for the given date and time."
  4. Otherwise writes the new document to Cosmos DB
note

The duplicate check is strict on both date AND time. Two records with the same date but different times are allowed.

StatusCause
200Record created, returns the created document
1403date or time missing, OR record already exists for this date/time combination

PUT /api/external/waterRatio/

Update an existing water ratio data point identified by date and time.

PUT /api/external/waterRatio/?date=09/11/2024&time=08:00:00
Authorization: Bearer <access_token>
Content-Type: application/json

{
"ratio": 0.90,
"produced": 10000,
"consumed": 9000
}
ParameterRequiredDescription
dateYesDD/MM/YYYY — identifies the record to update — 1403 if missing
timeYesHH:MM:SS — identifies the record to update — 1403 if missing

Body: JSON object with the fields to update.

How it works
  1. WaterRatioDataService(request_data).update_data() looks up the document by (industryId, date, time)
  2. If no record found → returns None → route returns 1403 "No data found on the given date"
  3. Otherwise updates the document fields with the request body values
StatusCause
200Record updated, returns the updated document
1403date or time missing, OR no record found for the date/time

Status Code Reference

CodeMeaning
200Success
404No data found for the query
401Missing or invalid JWT
420JWT token expired — obtain a new token via /api/user/user/login
440Session revoked — token was deactivated in token_logs (e.g. due to logout on another device)
1403Missing required parameter, or conflict (record already exists for POST / not found for PUT)