> ## Documentation Index
> Fetch the complete documentation index at: https://docs.telemax.com.au/llms.txt
> Use this file to discover all available pages before exploring further.

# Alert Monitoring Pipeline

> Fetch, filter, and acknowledge vehicle alerts using the V2 alert-records, alerts, and update-read-status endpoints.

## Overview

V2 uses standard pagination for alert retrieval — no sliding window or `num` cap required. Alerts are returned as `AlertDto` records from `GET /v2/api/companies/{id}/alert-records`.

Key fields per alert:

| Field               | Type    | Description                                       |
| ------------------- | ------- | ------------------------------------------------- |
| `id`                | integer | Unique alert identifier — use for acknowledgement |
| `utcTime`           | string  | When the alert occurred (UTC, no `Z` suffix)      |
| `userTime`          | string  | Alert time in the user's configured timezone      |
| `userTimeFormatted` | string  | Pre-formatted display string                      |
| `alertType`         | string  | Alert type identifier (e.g. `"18"`)               |
| `description`       | string  | Human-readable alert message                      |
| `vehicleId`         | integer | Vehicle that triggered the alert                  |
| `vehicleName`       | string  | Vehicle display name                              |
| `address`           | string  | Reverse-geocoded location                         |
| `lat` / `lng`       | double  | Location coordinates                              |
| `isRead`            | boolean | Whether the alert has been acknowledged           |

<Note>
  V2 adds `GET /v2/api/companies/{id}/alerts` which returns your **alert configurations** — including the human-readable `AlertType` string (e.g. `"Speeding"`, `"Geofence"`). Use this to build your type mapping instead of discovering it empirically.
</Note>

***

## Step 1 — Look up alert type names

Before polling, fetch your alert configurations to build a type-ID-to-name map:

```python Python theme={null}
import requests

def get_alert_config_map(company_id: int, token: str) -> dict:
    configs = {}
    page = 1
    while True:
        r = requests.get(
            f"https://api.telemax.com.au/v2/api/companies/{company_id}/alerts",
            params={"page": page, "pageSize": 50},
            headers={"Authorization": f"Bearer {token}"},
        )
        r.raise_for_status()
        data = r.json()
        for cfg in data.get("items", []):
            configs[cfg["alertId"]] = cfg["alertType"]   # e.g. {3631: "18"}
        if page >= data.get("numberOfPages", 1):
            break
        page += 1
    return configs
```

***

## Step 2 — Poll with pagination

`GET /v2/api/companies/{id}/alert-records` supports standard page/pageSize pagination. Filter by `IsRead` to process only unread alerts.

<CodeGroup>
  ```python Python theme={null}
  import os, time, requests

  API_BASE   = "https://api.telemax.com.au"
  API_KEY    = os.environ["TELEMAX_API_KEY"]
  COMPANY_ID = 85
  POLL_SEC   = 60

  _token, _expires_at = None, 0

  def get_token():
      global _token, _expires_at
      if _token and time.time() < _expires_at - 300:
          return _token
      r = requests.post(f"{API_BASE}/v2/api/authentication/token/api-key",
                        data={"apiKey": API_KEY})
      r.raise_for_status()
      d = r.json()
      _token, _expires_at = d["access_token"], time.time() + d["expires_in"]
      return _token

  def fetch_unread_alerts() -> list:
      alerts = []
      page = 1
      while True:
          r = requests.get(
              f"{API_BASE}/v2/api/companies/{COMPANY_ID}/alert-records",
              params={"page": page, "pageSize": 50},
              headers={"Authorization": f"Bearer {get_token()}"},
          )
          r.raise_for_status()
          data = r.json()
          unread = [a for a in data.get("items", []) if not a["isRead"]]
          alerts.extend(unread)
          if page >= data.get("numberOfPages", 1):
              break
          page += 1
      return alerts

  alert_types = get_alert_config_map(COMPANY_ID, get_token())

  while True:
      try:
          alerts = fetch_unread_alerts()
          for alert in alerts:
              process_alert(alert, alert_types)   # your handler — see Step 3
      except Exception as e:
          print(f"Poll error: {e}")
      time.sleep(POLL_SEC)
  ```

  ```javascript JavaScript theme={null}
  const API_BASE = "https://api.telemax.com.au";
  const COMPANY_ID = 85;
  let token = null, tokenExpiresAt = 0;

  async function getToken() {
    if (token && Date.now() < tokenExpiresAt - 300_000) return token;
    const res = await fetch(`${API_BASE}/v2/api/authentication/token/api-key`, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({ apiKey: "YOUR_API_KEY" }),
    });
    const { access_token, expires_in } = await res.json();
    token = access_token;
    tokenExpiresAt = Date.now() + expires_in * 1000;
    return token;
  }

  async function fetchUnreadAlerts() {
    const alerts = [];
    let page = 1;
    while (true) {
      const res = await fetch(
        `${API_BASE}/v2/api/companies/${COMPANY_ID}/alert-records?page=${page}&pageSize=50`,
        { headers: { Authorization: `Bearer ${await getToken()}` } },
      );
      const data = await res.json();
      alerts.push(...(data.items ?? []).filter(a => !a.isRead));
      if (page >= (data.numberOfPages ?? 1)) break;
      page++;
    }
    return alerts;
  }

  async function pollAlerts() {
    const alerts = await fetchUnreadAlerts();
    for (const alert of alerts) {
      handleAlert(alert);  // your handler — see Step 3
    }
  }

  pollAlerts();
  setInterval(pollAlerts, 60_000);
  ```
</CodeGroup>

***

## Step 3 — Filter by alert type

Use the config map from Step 1 to route alerts to the appropriate handler:

```python Python theme={null}
ALERT_HANDLERS = {
    "Speeding":       "overspeed",
    "Geofence":       "geofence",
    "DoorUnlocked":   "door_unlocked",
    "LowBattery":     "battery",
    # extend from your GET /v2/api/companies/{id}/alerts response
}

def process_alert(alert: dict, alert_types: dict):
    type_name  = alert_types.get(alert["alertType"], f"type_{alert['alertType']}")
    handler    = ALERT_HANDLERS.get(type_name, "unknown")
    print(f"[{handler}] {alert['vehicleName']} — {alert['description']} @ {alert['address']}")
    dispatch_notification(handler, alert)
```

***

## Step 4 — Acknowledge alerts

### Mark one alert as read

`PUT /v2/api/alerts/{id}/{date}/update-read-status` accepts a JSON body. In V2, `isRead` is **honoured** — you can mark an alert as unread by passing `false`.

```bash cURL theme={null}
curl -s -X PUT \
  "https://api.telemax.com.au/v2/api/alerts/101/2025-05-01T10:30:00/update-read-status?isRead=true" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

```python Python theme={null}
def mark_read(alert: dict, is_read: bool = True):
    requests.put(
        f"{API_BASE}/v2/api/alerts/{alert['id']}/{alert['utcTime']}/update-read-status",
        params={"isRead": str(is_read).lower()},
        headers={"Authorization": f"Bearer {get_token()}"},
    ).raise_for_status()
```

### Bulk clear all alerts

When you want to reset the unread state for the entire company:

```bash cURL theme={null}
curl -s -X PUT \
  "https://api.telemax.com.au/v2/api/companies/85/set-all-alerts-read" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

<Warning>
  `PUT /v2/api/companies/{id}/set-all-alerts-read` marks **all alerts for the company** as read. In multi-tenant or multi-integration setups, only call this when every consumer is caught up.
</Warning>

***

## Building notification rules

```python Python theme={null}
SEVERITY = {
    "overspeed":   "high",
    "geofence":    "medium",
    "door_unlocked": "medium",
    "battery":     "low",
}

CHANNELS = {
    "high":   lambda a: send_sms(a),
    "medium": lambda a: send_email(a),
    "low":    lambda a: log_to_dashboard(a),
}

def dispatch_notification(handler: str, alert: dict):
    severity = SEVERITY.get(handler, "low")
    CHANNELS[severity](alert)
```

***

## Gotchas

* **No sliding window needed.** V2 uses standard pagination — fetch all pages, filter by `IsRead: false`.
* **`isRead` is now honoured.** Unlike V1, V2's `update-read-status` correctly sets the read state to the value you pass.
* **`AlertType` names are available.** Use `GET /v2/api/companies/{id}/alerts` to get the string name for each `AlertType` integer — no need to build the mapping empirically.
* **`UtcTime` has no `Z` suffix** but is always UTC — treat it as UTC when parsing.
