Skip to main content

Website Map Integration — Live Map with Forecasts

This guide is for city governments and institutions that want to publish a live air quality map on their own website, as Kampala Capital City Authority (KCCA) does. It walks through running AirQo's open-source Leaflet map sample locally, deploying it on an existing website, and verifying it in production.

For a quick setup, use the hosted iframe embed below. Developers who want to customise and host the map themselves can follow the HTML or PHP instructions.

What visitors can explore​

The map shows public monitoring sites connected to your AirQo Grid ID.

FeatureWhat visitors can do
Live site readingsSee current measurements on colour-coded map markers.
7-day forecastsClick a site to explore daily forecasts, where available.
Site searchFind a monitoring site and open its details.
Heatmap overlaySwitch on the AQI heatmap to explore air quality across the area.

Choose your integration​

OptionBest forWhat you need
Quick embedAdding a map to an existing page or CMSYour Grid ID and a place to paste HTML
Developer: HTML or PHPCustomising the map code and hosting it yourselfYour Grid ID, an API token, and a Node.js backend

Quick embed — Copy. Paste. You're live.​

Add your Grid ID and paste the code into your website's HTML or custom HTML block. No API key or backend setup is needed on your website. The same iframe works on HTML and PHP websites.

Live site readings · 7-day forecasts · Heatmap overlay

1. Find your AirQo Grid ID​

Your Grid ID connects the map to the monitoring sites in your city or region. Follow Find your Grid ID.

2. Copy the embed code​

Open Add an air quality map to your website, enter your Grid ID, and select Copy embed code. Or copy the example below and replace YOUR_GRID_ID with your actual Grid ID:

Paste into your website's HTML or custom HTML block
<iframe
src="https://ai.airqo.net/website-map-integration?grid_id=YOUR_GRID_ID"
title="Air Quality Map"
style="width:100%;height:800px;border:0;display:block;"
loading="lazy"
></iframe>

3. Publish and check your map​

Save or publish the page, then open it in your browser. Check that your city's sites appear and that clicking a marker opens the site details. Adjust height:800px and the title to fit your page.

Using a CMS?

Paste the iframe into a custom HTML block or your CMS's code editor so it renders as a map. Once the map loads, your quick integration is complete.


Developer integration — HTML or PHP​

Use AirQo's open-source Leaflet sample when you want to customise the map code or run it on your own infrastructure. The remaining sections cover source files, local setup, deployment, and verification for developers.

The sample has two parts: a map page and a small Node.js service. The Node service supplies the Grid ID to the page and forwards API requests to AirQo, adding your access token on the server. The token never reaches the visitor's browser.

Choose the map page that fits your website. Both variants use the same Node service and proxy routes:

VariantUse it whenPage served by
HTMLYour website serves static pages, or you want the Node service to serve the page tooYour web server, or the Node service
PHPYour website runs on PHP (e.g. a CMS or custom PHP site)Your existing PHP handler

Pick your variant in the tabs below. Your choice applies to every tab on this page.

Already familiar with the endpoints?

This guide covers deployment only. For the data behind the map, see Recent Measurements, Spatial Heatmaps, and the Forecast API.


Source files​

Repository: github.com/airqo-platform/code-samples (staging branch). Use files from the same branch and folder together. They are not interchangeable between folders.

Folder: javascript/

FilePurpose
leaflet-with-forecast.htmlThe map page (Leaflet + jQuery, loaded from CDNs)
leaflet-forecast-server.jsNode.js service: config endpoint, allowlisted API proxy, health check. Can also serve the HTML page.
leaflet-forecast-nginx.conf.exampleReference Nginx routes. Do not publish it.

Requirements​

RequirementNotes
Node.js18 or newer when credentials are set as environment variables. The PHP variant's .env file support needs 20.12 or newer. Run it on a server that can keep a persistent process. The map page alone cannot work because it needs the proxy.
PHP (PHP variant only)Your existing PHP hosting. For local testing, the php command-line executable with allow_url_fopen enabled (the default).
An AirQo API access tokenSee Step 1. Forecasts require a Premium tier token. See Pricing & Access Tiers.
Your city's Grid IDSee Finding your Grid ID, or ask AirQo support
An HTTPS websitePlus a way to route selected paths to the Node service on the same domain (e.g. Nginx, Apache, IIS, or an equivalent platform rewrite)
Network accessServer → https://api.airqo.net (outbound). Browser → unpkg.com (Leaflet), code.jquery.com, and OpenStreetMap tiles. If your site sets a Content Security Policy, allow these.

No npm install or Composer install is needed. The Node service uses only Node built-ins, and the browser loads its libraries from CDNs.

PHP-only shared hosting

Hosting that runs PHP but cannot run a persistent Node process cannot host this proxy. Run the Node service on a separate backend and route the website's /airqo-config and /airqo-api/ paths to it on the same domain, or implement equivalent proxy routes in PHP.


Step 1 — Get your access token​

Create an API client and generate a token in Nexus as described in Authentication & Setup → Generate your API credentials. If your organisation already has a client for its city sensors, open API settings in Nexus and reuse that client's token. You do not need a new client for this integration.

This token will be used only on the server. Configure the client to match:

If the token is suspended or needs replacing, you do not need a new client:

  • Suspended: choose Reinstate on the client. See Auto-suspension.
  • Replace: choose Regenerate Token on the existing client. The new token replaces the old one, so update every integration that uses this client. See Token rotation.

After changing the token, update it in the server's environment and restart the Node service.

Keep the token server-side

Never put the token in the map page, client-side JavaScript, a public repository, a browser-visible URL, a "public" build-time environment variable, a screenshot, or a support message.


Step 2 — Run the sample locally​

The Node service serves the HTML page and the proxy together, so one terminal is enough. Clone the staging branch and run the server from the repository root.

macOS / Linux:

git clone --branch staging https://github.com/airqo-platform/code-samples.git
cd code-samples
export AIRQO_API_TOKEN="YOUR_ACCESS_TOKEN"
export AIRQO_GRID_ID="YOUR_GRID_ID"
node javascript/leaflet-forecast-server.js

Windows (PowerShell):

git clone --branch staging https://github.com/airqo-platform/code-samples.git
cd code-samples
$env:AIRQO_API_TOKEN = "YOUR_ACCESS_TOKEN"
$env:AIRQO_GRID_ID = "YOUR_GRID_ID"
node .\javascript\leaflet-forecast-server.js

Keep the terminal running and open http://127.0.0.1:8080/. Do not open the HTML file directly as a file:// URL. It needs the server's proxy.


Step 3 — Deploy on your existing website​

How it fits together​

The page calls root-level paths on its own domain. Your web server must route them to the Node service:

PathRouted to Node?Purpose
The map page (e.g. /air-quality/ or /leaflet-with-forecast.php)No. Served by your web server or PHP.The map
/airqo-configYesReturns {"gridId":"..."} to the page
/airqo-api/YesAllowlisted proxy to api.airqo.net
/healthzOptionalReturns {"status":"ok"} for monitoring

The page uses absolute paths for /airqo-config and /airqo-api/, so a page placed in a subdirectory still calls the root paths. If your website already uses either path, change the paths in the page and in your proxy rules together. Do not route the whole domain to the Node service.

Place the files​

  • Copy leaflet-with-forecast.html to a location your web server serves, e.g. /var/www/airqo/leaflet-with-forecast.html.
  • Put leaflet-forecast-server.js in a private directory, e.g. /opt/airqo/leaflet-forecast-server.js.

The Node service can also serve the page itself at / and /leaflet-with-forecast.html if both files stay together in one directory. On an existing website, it is usually simpler to let your normal web server serve the HTML.

Run the Node service​

Configure these as service environment variables in your service manager (e.g. systemd) or deployment platform, with the token in its secret store. Do not paste them as a shell script:

VariableValue
NODE_ENVproduction
HOSTSame-host proxy (e.g. Nginx): 127.0.0.1, so the service listens internally only. Managed platform or container: the address the platform can reach, usually 0.0.0.0. Set it explicitly: the HTML variant defaults to 0.0.0.0, the PHP variant to 127.0.0.1.
PORTSame-host proxy: 8080, which must match proxy_pass. Managed platform or container: leave unset if the platform injects PORT, otherwise use the port the platform routes to.
AIRQO_API_TOKENYour access token (store it as a secret)
AIRQO_GRID_IDYour city's Grid ID

Start command: node /opt/airqo/leaflet-forecast-server.js

In production the server refuses to start unless both AIRQO_API_TOKEN and AIRQO_GRID_ID are set. Run it under a service manager so it restarts after a crash or reboot.

Port numbers

This guide uses 8080 for the Node service throughout. The javascript/ README and its Nginx example use 8081. Any free port works, as long as PORT and every proxy_pass match.

Nginx example​

Add these locations inside your existing HTTPS server block. proxy_pass has no trailing slash, so the original request path is passed through unchanged. ^~ stops regex locations elsewhere in your config (such as a PHP or static-asset handler) from capturing /airqo-api/ requests.

location = /air-quality/ {
alias /var/www/airqo/leaflet-with-forecast.html;
default_type text/html;
}

location = /airqo-config {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location ^~ /airqo-api/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

# Optional, for monitoring only:
location = /healthz {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
}

Change the alias if you copied the HTML elsewhere.

Validate and reload:

sudo nginx -t && sudo systemctl reload nginx

Apache, IIS, or your hosting platform can provide equivalent reverse-proxy routes. If your website and backend run on different platforms, configure equivalent same-origin rewrites from the website to the backend. Do not set up an unrestricted public CORS proxy.

Embed in an existing page (optional)​

Once the standalone map works, you can show it inside any page or CMS HTML block with an iframe. Adjust the src path and height:

<iframe src="/air-quality/"
title="Air quality forecast map"
style="width:100%;height:700px;border:0;"
loading="lazy"></iframe>

leaflet-forecast-server.js is a backend program. Never load it with a browser <script> tag.


How requests flow​

  1. The browser loads the map page and requests /airqo-config, which returns {"gridId":"..."}. The Grid ID is public, but the token is not.

  2. The page requests, through the proxy:

    Browser path (after /airqo-api)Data
    /api/v2/devices/measurements/grids/{gridId}Current measurements
    /api/v2/predict/daily-forecasting/{gridId}Daily forecasts
    /api/v2/spatial/heatmaps/{gridId}Heatmap image and bounds (only when the heatmap control is switched on)
  3. The Node service allows only GET requests to these three paths for its configured Grid. It drops any token parameter supplied by the browser, adds the server-side token, and passes the AirQo response back.

  4. The page joins measurements and forecasts by site_id. A site with no matching forecast shows a "no forecast" message.

The proxy is public

The token stays private, but /airqo-config and /airqo-api/ can be called by anyone who can reach your website, and those calls count against your token. If you need to restrict access, add website authentication, rate limits, or caching in front of these routes. See Security and operations.


Using the map​

  • Search finds a monitoring site. Click a marker or a search result to open the site panel.
  • Select a forecast day to see the expected PM2.5 range and forecast confidence. Expand Meteorological conditions for temperature, humidity, and wind, where provided.
  • Heatmap control (coloured map icon) toggles the AQI heatmap overlay.
  • Site values control (numbered pin, PHP variant): markers show the rounded PM2.5 reading by default. Use this control to hide or restore the numbers. Units (µg/m³) appear in the site popup.

A blue background shows that a control is active. Forecasts depend on API coverage and matching site identifiers, so a site can have current measurements but no forecast or meteorological fields.


Step 4 — Verify the deployment​

Replace example.org and the page path with your own:

curl -i https://example.org/airqo-config   # {"gridId":"..."}, with no token
curl -i https://example.org/air-quality/ # 200, served over HTTPS
curl -i https://example.org/healthz # {"status":"ok"} (if exposed)

/healthz only confirms that the Node process responds. It does not test your token or data availability, so always check a real data request too.

Then, in a browser:

  • Markers appear on the map
  • Search selects a site, and clicking a site opens its forecast panel
  • The heatmap overlay loads when switched on
  • In the browser's Network tab, /airqo-api/ requests succeed, and no request URL or page source contains your token
  • leaflet-forecast-server.js and any .env file cannot be downloaded from your website

If a panel is empty, inspect the /airqo-api/ requests in the Network tab. A missing forecast for an individual site can be legitimate, as long as its site_id matches the live measurement and the Grid has forecast coverage.


Troubleshooting​

These issues are specific to this integration. For API status codes in general, see Error Codes.

SymptomWhat to check
Blank page, or /airqo-config failsIs /airqo-config routed to the Node service, and is the service running?
Node exits at startupBoth AIRQO_API_TOKEN and AIRQO_GRID_ID must be set in production. Check that PORT is free and valid. For .env loading, Node must be 20.12 or newer.
401 / 403 on /airqo-api/ callsThe server-side token: is it the real token rather than a template placeholder, not suspended, and on a tier that includes the endpoint (forecasts need Premium)? A stale shell variable can also override .env. See 401 and 403.
404 with "AirQo API route not allowed."The request is not one of the three allowlisted paths for the configured Grid. Check the Grid ID and your path rewrite.
404 from AirQoCheck the Grid ID. See 404 Not Found.
502 "Could not reach the AirQo API."Outbound connectivity from the server to api.airqo.net, and the service logs. Upstream requests time out after 30 seconds.
502 from your web serverThe Node service is not running or not on the port in proxy_pass. Check /healthz on the server itself, e.g. curl http://127.0.0.1:8080/healthz.
Markers but no forecastsMeasurements and forecasts are separate requests. Check the forecast response and your tier, and confirm that forecast site_ids match measurement site_ids.
Map tiles or libraries do not loadBrowser access to unpkg.com, code.jquery.com, and OpenStreetMap tiles, and your site's Content Security Policy.
PHP, local: php or node not foundInstall the runtime and add it to your PATH.
PHP, local: 404 on /airqo-configStart PHP with the router command from Step 2 and open the page on port 8082. The page shows "Server configuration unavailable" in this case.
PHP, local: 502 "Cannot reach the Node service on port 8080"Start Node with PORT=8080, enable allow_url_fopen in PHP, and clear stale PORT / HOST shell variables.

Never paste a real token into a support ticket, screenshot, or browser console.


Security and operations​

  • Keep credentials out of the public web directory, Git, browser JavaScript, public environment settings, URLs, and screenshots.
  • Bind Node to 127.0.0.1 when your web server runs on the same host, and keep the Node port closed to the public.
  • Serve the page over HTTPS only.
  • Store the token in your platform's secret store, restart Node after any credential change, and monitor /healthz.
  • The proxy routes are publicly callable. Add website authentication or rate limits if you need to restrict who can retrieve the data.
  • For high-traffic public pages, consider caching responses at the proxy (heatmaps and forecasts change infrequently; see Best Practices → Caching). The sample proxy sends Cache-Control: no-store, max-age=0, so HTTP caches such as Nginx proxy_cache will not store its responses as-is. To cache public Grid data, either change that header in leaflet-forecast-server.js or override it at your cache with an explicit TTL. In Nginx, caching must first be enabled: define a cache zone in the http block, e.g. proxy_cache_path /var/cache/nginx/airqo keys_zone=airqo:10m;. Then, in the /airqo-api/ location, turn it on with proxy_cache airqo; and add proxy_ignore_headers Cache-Control; and proxy_cache_valid 200 30m;.
  • Review AirQo's API usage terms and fair usage policy before going live.

Attribution​

AirQo data is open access provided it is used with appropriate attribution. Both variants show a "Powered by AirQo" link in the map's bottom-right corner. Keep it visible.

Both variants also blank the OpenStreetMap tile credit, which the OpenStreetMap tile usage policy requires. Restore it before going live:

L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);

We also recommend a short acknowledgement near the map, for example:

Air quality data and forecasts provided by AirQo, Makerere University.


Next steps​