# MyAQI.org — Device Data API

Devices push air-quality readings to MyAQI over HTTP. MyAQI ingests them into the Kafka schema on our side. We can also build a custom adapter to a vendor's own format, as long as it carries the mandatory fields below.

> **Recommended:** We support `HTTP GET` (URL query parameters) for simple or legacy devices, **but we prefer `HTTPS POST` with a JSON body** — it is secure (TLS), supports batching to save device battery, and avoids putting data and tokens in URLs/logs. New integrations should use HTTPS + POST + JSON where possible.

---

## Authentication

Every request must include a per-device **API token** issued by MyAQI. Send it in the `Authorization` header as a Bearer token:

```
Authorization: Bearer <apitoken>
```

- The token is issued per device (or per vendor fleet) during onboarding and maps to the device's `imei`.
- **Preferred:** send the token in the `Authorization` header over **HTTPS**, so it is never exposed in URLs or server logs.
- **Legacy fallback:** for constrained devices that cannot set headers, the token may be passed as a query parameter `?apitoken=<apitoken>` on a GET. This is **discouraged** — tokens in URLs can leak into logs and proxies — and should only be used over HTTPS.
- Requests with a missing, invalid, or revoked token receive `401 Unauthorized`.
- A token that does not match the `imei` in the payload receives `403 Forbidden`.

---

## Endpoints

| Method | URL | Body | Use |
|--------|-----|------|-----|
| **POST** (preferred) | `https://devices.myaqi.org/readings` | JSON | New integrations; supports single or batch readings |
| GET (supported) | `http(s)://devices.myaqi.org/readings?…` | query params | Simple/legacy devices; one reading per call |

- **Success:** `200 OK` / `202 Accepted` with a small JSON acknowledgement.
- **Errors:** `401` (bad/missing token), `403` (token/imei mismatch), `400` (malformed payload or missing mandatory field).

---

## Field reference

| # | Field | Type | Unit | Required | Notes |
|---|-------|------|------|----------|-------|
| 1 | `imei` | string | — | **Yes** | Device id (<=30 chars); is a unique id for each device. |
| 2 | `lat` | number | degrees | **Yes** | Latitude, e.g. `18.55348`. |
| 3 | `lon` | number | degrees | **Yes** | Longitude, e.g. `73.807503`. |
| 4 | `timestamp` | string | ISO 8601 | **Yes** | `YYYY-MM-DDThh:mm:ss.sssZ` (UTC, with milliseconds). |
| 5 | `pm1` | number | µg/m³ | No | PM1.0 concentration. |
| 6 | `pm25` | number | µg/m³ | No | PM2.5 concentration. |
| 7 | `pm10` | number | µg/m³ | No | PM10 concentration. |
| 8 | `so2` | number | µg/m³ | No | Sulphur dioxide. |
| 9 | `no2` | number | µg/m³ | No | Nitrogen dioxide (NOx accepted in lieu of NO₂). |
| 10 | `co` | number | ppm | No | Carbon monoxide. |
| 11 | `co2` | number | ppm | No | Carbon dioxide. |
| 12 | `hcho` | number | µg/m³ | No | Formaldehyde. |
| 13 | `temp` | number | °C | No | Temperature (Celsius). |
| 14 | `humid` | number | g/m³ | No | Humidity. |
| 15 | `alt` | number | m | No | Altitude above ground level (AGL). |
| 16 | `rssi_4g` | number | dBm | No | 4G signal strength; negative (e.g. `-75`). |
| 17 | `rssi_5g` | number | dBm | No | 5G signal strength; negative. |
| 18 | `bat` | number | % | No | Battery level, `0`–`100`. |

*Additional fields may be added over time; unknown fields are ignored. Measurement values are JSON **numbers** (e.g. `33.0`, not `"33.0"`); only `imei` and `timestamp` are strings. Optional fields may be **omitted** (preferred) or sent as `null` — never as empty strings.*

---

## Preferred — HTTPS POST + JSON (single reading)

```json
{
  "imei": "E868E7C7CA23",
  "lat": 18.55348,
  "lon": 73.807503,
  "timestamp": "2026-05-06T14:01:00.000Z",
  "pm1": 21.0,
  "pm25": 33.0,
  "pm10": 43.0,
  "temp": 33.9,
  "humid": 28.1,
  "rssi_4g": -75,
  "bat": 85
}
```

```bash
curl --location 'https://devices.myaqi.org/readings' \
  --header 'Authorization: Bearer <apitoken>' \
  --header 'Content-Type: application/json' \
  --data '{
    "imei": "E868E7C7CA23",
    "lat": 18.55348,
    "lon": 73.807503,
    "timestamp": "2026-05-06T14:01:00.000Z",
    "pm1": 21.0,
    "pm25": 33.0,
    "pm10": 43.0,
    "temp": 33.9,
    "humid": 28.1,
    "rssi_4g": -75,
    "bat": 85
  }'
```

---

## Preferred — HTTPS POST + JSON (batch upload)

To conserve battery, a device may cache readings and upload several at once (e.g. every 5–15 minutes). Send identity once and an array of time-stamped readings:

```json
{
  "imei": "E868E7C7CA23",
  "lat": 18.55348,
  "lon": 73.807503,
  "readings": [
    { "timestamp": "2026-05-06T14:01:00.000Z", "pm25": 33.0, "pm10": 43.0, "temp": 33.9, "humid": 28.1, "bat": 85 },
    { "timestamp": "2026-05-06T14:16:00.000Z", "pm25": 35.0, "pm10": 46.0, "temp": 34.1, "humid": 27.4, "bat": 84 }
  ]
}
```

```bash
curl --location 'https://devices.myaqi.org/readings' \
  --header 'Authorization: Bearer <apitoken>' \
  --header 'Content-Type: application/json' \
  --data '{
    "imei": "E868E7C7CA23",
    "lat": 18.55348,
    "lon": 73.807503,
    "readings": [
      { "timestamp": "2026-05-06T14:01:00.000Z", "pm25": 33.0, "pm10": 43.0, "temp": 33.9, "humid": 28.1, "bat": 85 },
      { "timestamp": "2026-05-06T14:16:00.000Z", "pm25": 35.0, "pm10": 46.0, "temp": 34.1, "humid": 27.4, "bat": 84 }
    ]
  }'
```

`imei` is required at the top level. `lat`/`lon` may be given once at the top level (stationary device) or per-reading. Each element of `readings` must include `timestamp`.

---

## Supported — HTTP GET + URL (single reading, legacy)

For simple or legacy devices. Prefer HTTPS; prefer the `Authorization` header over the `apitoken` query parameter.

```bash
# Token in header (preferred, over HTTPS)
curl --location --get 'https://devices.myaqi.org/readings' \
  --header 'Authorization: Bearer <apitoken>' \
  --data-urlencode 'imei=E868E7C7CA23' \
  --data-urlencode 'lat=18.55348' \
  --data-urlencode 'lon=73.807503' \
  --data-urlencode 'timestamp=2026-05-06T14:01:00.000Z' \
  --data-urlencode 'pm1=21.0' \
  --data-urlencode 'pm25=33.0' \
  --data-urlencode 'pm10=43.0' \
  --data-urlencode 'temp=33.9' \
  --data-urlencode 'humid=28.1' \
  --data-urlencode 'rssi_4g=-75' \
  --data-urlencode 'bat=85'
```

```
# Token in URL (discouraged — only if headers are impossible, HTTPS only)
https://devices.myaqi.org/readings?apitoken=<apitoken>&imei=E868E7C7CA23&lat=18.55348&lon=73.807503&timestamp=2026-05-06T14%3A01%3A00.000Z&pm25=33.0&pm10=43.0&temp=33.9&humid=28.1&rssi_4g=-75&bat=85
```

On a GET, omit optional parameters entirely (don't send empty `so2=`). Colons in the timestamp are percent-encoded (`%3A`) in a raw URL.

---

## Rules & conventions

- **Authenticate every request** with the API token (`Authorization: Bearer <apitoken>` preferred).
- **Mandatory fields:** `imei`, `lat`, `lon`, `timestamp` (for a batch, `timestamp` per reading; `imei`/`lat`/`lon` at the top level).
- **Timestamps** are UTC, full ISO 8601 with milliseconds and trailing `Z`.
- **Optional fields:** omit the key (preferred) or send `null`; never empty strings.
- **Numbers** are unquoted JSON numbers; `rssi_4g`/`rssi_5g` are negative.
- **Transport:** HTTPS strongly preferred for all traffic; required if the token is passed in a URL.
- **Custom formats:** MyAQI can build an adapter to a vendor's own shape provided the mandatory fields are derivable.
