Skip to main content

AQI monitoring for the city of Montréal (Québec, Canada)

Latest Release Python CI Test Coverage PyPI Downloads License Code style: Ruff

A Python library and CLI tool to fetch, process, and expose air quality index (AQI) data from the City of Montréal open data platform.

We designed this project to provide:

  • scriptable output (JSON by default)
  • embeddable components as a Python library
  • automation-ready tools (Home Assistant, cron jobs, data pipelines)

Features

  • Fetches the latest air quality data from Montréal’s open data portal
  • Lists active air quality monitoring stations
  • Computes the AQI based on RSQA
  • Estimates pollutant concentrations from reported AQI contributions¹
  • Exposes structured station and pollutant data via Python models
  • Outputs machine-readable JSON from the CLI
  • Structured logging with optional debug mode
  • Test suite covering core logic, JSON contract, and CLI behavior

¹ Estimated concentrations come from rounded AQI values; treat them as approximations.

Performance & Optimization

  • Intelligent caching (TTL-based, max 100 entries)
  • Batch requests via get_stations_aqi() for parallel multi-station queries
  • Server-side filtering, sorting, and column selection (fields parameter)
  • Pagination support with offset parameter for large datasets
  • Debug logging includes full API URLs and request parameters

Requirements

  • Python 3.11 or newer
  • requests

Installation

pip install montreal-aqi-api

From source

git clone https://github.com/normcyr/montreal-aqi-api.git
cd montreal-aqi-api
python3 -m venv venv
source venv/bin/activate
pip install .

CLI Usage

The CLI always outputs JSON on stdout and writes logs and diagnostics to stderr.

Fetch AQI for a specific station

montreal-aqi --station <station_id>

List available monitoring stations

montreal-aqi --list

Enable debug logging

montreal-aqi --station <station_id> --debug

Print the JSON in a pretty format

montreal-aqi --station <station_id> --pretty

or

montreal-aqi --list --pretty

No arguments

When you provide no arguments, the CLI returns a JSON error payload. We intentionally avoid interactive prompts to keep behavior predictable in automated environments.

Advanced examples

Fetch multiple stations

montreal-aqi --station 1,2,3 --pretty

Suppress output (for scripts)

montreal-aqi --station 80 --quiet
# No output if successful, useful for cron jobs

Verbose logging

montreal-aqi --station 80 --verbose
# Shows detailed logs including API request times and cache status

Combine options

montreal-aqi --list --pretty --verbose

Integrations

Use this library to integrate AQI monitoring with automated systems and workflows.

Home Assistant

Used by the custom Home Assistant integration:

Other Use Cases

  • Cron jobs
  • Data ingestion pipelines
  • Monitoring dashboards
  • Research / environmental analysis

JSON Contract — Version 1 (Frozen)

As of v0.4.0, we explicitly versioned and froze the JSON output contract. We formally specify the output format in: docs/json_contract_v1.md.

The official JSON Schema v1 governs the JSON output, and all payloads include:

{
  "version": 1,
  "type": "..."
}

Error Payload

{
  "version": 1,
  "type": "error",
  "error": {
    "code": "NO_DATA",
    "message": "No data available for this station"
  }
}

Stations List Payload

{
  "version": 1,
  "type": "stations",
  "stations": [
    {
      "station_id": "3",
      "name": "Saint-Jean-Baptiste",
      "borough": "Rivière-des-Prairies"
    }
  ]
}

Station AQI Payload

{
  "version": 1,
  "type": "station",
  "station_id": "80",
  "date": "2025-08-08",
  "hour": 10,
  "aqi": 49,
  "dominant_pollutant": "PM2.5",
  "pollutants": {
    "PM2.5": {
      "name": "PM2.5",
      "aqi": 49,
      "concentration": 34.3
    },
    "O3": {
      "name": "O3",
      "aqi": 22,
      "concentration": 70.4
    }
  }
}

Python Usage

Fetch AQI for a single station

from montreal_aqi_api.service import get_station_aqi

station = get_station_aqi("80")
if station:
    print(station.to_dict())

Fetch AQI for multiple stations (parallel requests)

For better performance when fetching data for multiple stations, use get_stations_aqi():

from montreal_aqi_api.service import get_stations_aqi

# Fetch AQI for multiple stations concurrently (default: 5 workers)
stations = get_stations_aqi(["1", "3", "5", "80"])

for station in stations:
    if station:
        print(f"Station {station.station_id}: AQI={station.aqi}")
    else:
        print("Failed to fetch data")

Domain objects (Station, Pollutant) expose explicit serialization helpers:

station = get_station_aqi("80")
data = station.to_dict()  # Returns fully serialized JSON-compatible dict
print(data["pollutants"]["PM2.5"])  # Access individual pollutants

Exit codes

The montreal-aqi CLI exits with explicit status codes to make it suitable for scripting, automation, and CI pipelines.

Exit code Meaning Description
0 Success Command executed successfully
1 Generic API error An unexpected internal API error occurred
2 API unreachable The CLI could not reach the Montreal Open Data API (network error, timeout, DNS, etc.)
3 Invalid API response API returned malformed or unexpected JSON payload

Details

  • The CLI reports all errors both via:
    • a structured JSON error payload on stdout
    • a non-zero process exit code
  • This ensures compatibility with:
    • shell scripts
    • cron jobs
    • CI/CD pipelines
    • Home Assistant / automation tools

Example

$ montreal-aqi --station 80
{
  "version": "1",
  "type": "error",
  "error": {
    "code": "API_UNREACHABLE",
    "message": "Montreal open data API is unreachable"
  }
}

$ echo $?
2

AQI Methodology

AQI values follow the methodology defined by the Réseau de surveillance de la qualité de l’air (RSQA).

Reference Values

Pollutant Full Name Reference
SO₂ Sulfur Dioxide 500 µg/m³
CO Carbon Monoxide 35 mg/m³
O₃ Ozone 160 µg/m³
NO₂ Nitrogen Dioxide 400 µg/m³
PM2.5 Particulate Matter 35 µg/m³

Project Status

  • JSON contract v1 frozen and validated by tests
  • Suitable for automation and integration
  • API stability guaranteed within v1

Contributing

Contributions are welcome.

Please ensure that:

  • tests pass (pytest)
  • the JSON contract remains backward compatible
  • tests cover any change affecting output
  • lint and format code with ruff (run ruff format --check . and ruff check .)

Open an issue before proposing breaking changes.


Data Source

Data retrieved from the Ville de Montréal Open Data Portal.


License

We license this project under the MIT License.

Metadata

Release files for montreal-aqi-api 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for montreal-aqi-api 0.8.0
File Size Uploaded Provenance Actions
montreal_aqi_api-0.8.0.tar.gz 26.8 kB Without provenance Details for montreal_aqi_api-0.8.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for montreal-aqi-api 0.8.0
File Interpreter ABI Platform Actions
montreal_aqi_api-0.8.0-py3-none-any.whl Python 3 none any Details for montreal_aqi_api-0.8.0-py3-none-any.whl

Total release size: 43.6 kB

Release files / montreal_aqi_api-0.8.0.tar.gz

Download URL montreal_aqi_api-0.8.0.tar.gz
Size 26.8 kB
Tags Source
SHA-256 checksum
How to use checksums
79ea245db0e62ef1743285e690e7c44f373ca8f6c85d0f4d6abcfb6359522379
BLAKE2b-256 checksum
How to use checksums
c097ebb662c95ad5343d12c0f3575e80ec05792093400fc45c6d9de4ef22d9f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.17

Release files / montreal_aqi_api-0.8.0-py3-none-any.whl

Download URL montreal_aqi_api-0.8.0-py3-none-any.whl
Size 16.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
41326b6de8ade023884c8f3a7fdbad4dbf5cccc8e8c77a6544289081567c972a
BLAKE2b-256 checksum
How to use checksums
074c87a64aef575c1895f4ef8fc06d7ae717b9637cf54124a055fb967fea3449
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.17

Security for montreal-aqi-api 0.8.0

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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page