# Haze Monitor by Mohtasham Murshid Madani

[Canonical page](https://www.mohtasham.dev/work/hazemonitor)

A free, ad-free haze site for Malaysia and Singapore. 18K people used it in four weeks, most of them sent by Bing, Google and ChatGPT.

Isaac Tan and I built Haze Monitor for the 2026 haze season. It shows live DOE Malaysia and NEA Singapore readings over illustrated city scenes, with a page for every station, city and state. From October 2 to 29, Bing sent 5.3K first-time visitors, Google 3.9K and ChatGPT 2.7K.

Role: Co-creator. Updated 2026-11-01.

I started Haze Monitor at the end of August 2026, just before haze season, and built it with Isaac Tan. It reads DOE Malaysia's live station feed and Singapore's NEA readings, then shows the nearest reading over an illustrated picture of your city. The haze in the picture thickens with the number. It is free and has no ads.

From October 2 to 29, 18K people used it. Bing sent 5.3K first-time visitors, more than Google's 3.9K, and ChatGPT sent 2.7K. This page is about the search and AI-answer work behind those numbers.

## My work

- Started the project
- Design and city artwork
- Search and AI answers

## Oct 2 to 29, 2026

- 18K active users
- 155K events
- Visitors from 76 countries

## First visits by source

- Bing 5.3K
- Google 3.9K
- ChatGPT 2.7K

## Pages that answer in the HTML

The first version fetched every reading in the browser. A crawler got a page title, a heading and a dash where the number should be. Isaac fixed that first. He moved the readings to the server so the number arrives in the HTML, then added a short summary under each city in English and Malay. Malaysians search in both languages, with words like 'jerebu', 'IPU' and 'indeks pencemaran udara'.

He also gave every place its own page. Fourteen city pages had left most of the country with nothing to land on, so he added one for each of the 68 DOE stations and 16 states. The URLs use the names people type, so Penang is /state/penang, not pulau-pinang. The sitemap grew from 17 URLs to 101. It has 272 now.

My part was matching each page to the words people use. Malaysia's official index is called API, the Air Pollutant Index. Searchers also type AQI. We calculate a US AQI from DOE's PM2.5 readings, show it next to the official API, and put both terms in the titles, as in 'Malaysia AQI & Haze Today (API/IPU)'. The Malaysia and Singapore reports also have Malay and Simplified Chinese versions linked with hreflang. The Chinese Malaysia report is one of the three most-viewed pages on the site.

## Making it easy for AI assistants to quote

I wanted ChatGPT to cite Haze Monitor when someone asked it about the haze. I ran the site through is-agentic.com, which scores how well AI agents can read and use a website, and worked through what it flagged.

Every public page now returns clean Markdown when a client sends Accept: text/markdown, and has a matching .md URL. llms.txt tells an assistant which page answers which question and how to read the numbers. It explains that Malaysia's API is not the US AQI and that every reading has an observation time to check. Missing pages return a real 404 with links back. An OpenAPI 3.1 file describes the six public JSON endpoints. They need no key and return errors with a stable code and a hint for recovering.

hazemonitor.com scores 97 out of 100 on is-agentic, with all eight essential checks passing. In four weeks ChatGPT sent 2.7K first-time visitors, more than Yahoo and DuckDuckGo combined. Bing surprised me more. The only search tool I used was Google Search Console, to submit the sitemap. Bing found the site on its own and still sent more first visits than Google.

## Numbers people can trust

A haze site that shows the wrong number is worse than none. Every reading keeps its station name and observation time. If a station stops reporting, the page keeps its last reading and labels it as old. It never borrows a reading from a station farther away. A D1 archive on Cloudflare fills gaps when the DOE or NEA feed fails, and it keeps the original timestamps.

The AQI conversion needed the most care. On September 21 I found we were still using DOE's 2021 PM2.5 formula. For Cheras, it turned an official API of 139 into a US AQI of 185. The current formula gives 147, which matches DOE's own 24-hour PM2.5 figure. I checked the corrected table against all 67 stations that report both values, and every one agreed within one index point.

Weekly reports give people and search engines something new each week. Malaysia, Singapore, Thailand, Indonesia and Vietnam each have one, with archives by ISO week. The Thailand, Indonesia and Vietnam reports use CAMS model estimates. They are labelled as estimates and never enter the archive of measured readings.

## How a question reaches a reading

1. Someone asks: They search 'jerebu KL' or ask ChatGPT about the haze.

2. A page matches: Every station, city and state has its own URL in the sitemap.

3. The HTML answers: The reading, category and time are rendered on the server.

4. Assistants get Markdown: llms.txt and Accept: text/markdown give them a clean copy to quote.

5. The page stays fresh: Pages rebuild every five minutes from DOE and NEA.

## Where the numbers come from

Sources: DOE Malaysia: 68 stations, hourly API and PM2.5; NEA Singapore: PSI and PM2.5 for five regions; Open-Meteo CAMS: Model estimates for Thailand, Indonesia and Vietnam

Store: Collector Worker: Saves readings on a schedule; D1 archive: Fills gaps and keeps original timestamps

Serve: Cloudflare Worker: Pages rebuilt every five minutes; Public API: Six read-only JSON endpoints with OpenAPI 3.1

Read: People: Illustrated city pages; Search engines: Server-rendered HTML and a 272-URL sitemap; AI assistants: llms.txt and Markdown versions

## what people search to what Haze Monitor answers with

- jerebu hari ini: A Malay summary on every city page. With API/IPU facts and DOE health guidance

- Malaysia AQI: US AQI next to the official API. Calculated from DOE PM2.5, with the method shown

- 马来西亚 烟霾: A Simplified Chinese country report. Linked to the English and Malay versions with hreflang

- air quality Petaling Jaya: A page for every station, city and state. 68 stations and 16 states, named the way people type them

- Singapore PSI today: A Singapore report with PSI and PM2.5. Five regions, each with its own observation time

- haze last week: Weekly reports. Five countries, with archives by ISO week

## Read it like an assistant

```text
# The Malaysia report as Markdown
curl -H 'Accept: text/markdown' https://hazemonitor.com/malaysia

# Which page answers which question
curl https://hazemonitor.com/llms.txt

# Six read-only JSON endpoints, no key needed
curl https://hazemonitor.com/openapi.json
```

curl -H 'Accept: text/markdown' https://hazemonitor.com/malaysia

Every public page has a Markdown version, and llms.txt points assistants to the right one.

Isaac and I made a haze site people could read at a glance. The traffic came from making it just as easy for search engines and AI assistants to read.

- [Open Haze Monitor ↗](https://hazemonitor.com)
- [Read llms.txt ↗](https://hazemonitor.com/llms.txt)
- [Developer guide ↗](https://hazemonitor.com/developers)
- [is-agentic report ↗](https://is-agentic.com/scan/hazemonitor.com)

[Visit Haze Monitor](https://hazemonitor.com)
