Korean real-time air quality (PM10, PM2.5, O3, NO2, CO, SO2) and 1–3 day forecasts via 한국환경공단 에어코리아 OpenAPI (apis.data.go.kr/B552584). Six subcommands wrap g...
---
name: airkorea-cli
description: Korean real-time air quality (PM10, PM2.5, O3, NO2, CO, SO2) and 1–3 day forecasts via 한국환경공단 에어코리아 OpenAPI (apis.data.go.kr/B552584). Six subcommands wrap getMsrstnAcctoRltmMesureDnsty (측정소별 실시간), getCtprvnRltmMesureDnsty (시도별 실시간), getMinuDustFrcstDspth (예보), getMsrstnList (측정소 목록), getTMStdrCrdnt (행정동→TM좌표), and getNearbyMsrstnList (TM→근접측정소). Use when building air-quality dashboards, mask/runner notifications, school outdoor-activity advisories, location-based AQI lookups by 행정동 / 시도 / 좌표, or grounding LLM answers about Korean air quality in regulator-curated data. Pairs with juso-address-cli (resolve addresses), kakao-local-cli (WGS84 coords), kr-holiday-cli (calendar overlay), naver-papago-translate (localize for non-KR users), and tistory-api-cli/velog-cli (publish AQI digests). Free data.go.kr tier (1,000 req/day dev, higher on prod approval).
version: 0.1.0
license: MIT
---
# airkorea-cli
Command-line wrapper for **한국환경공단 에어코리아 OpenAPI** — Korea's first-party air-quality system. Operated by the Korea Environment Corporation, it is the data behind airkorea.or.kr, every weather-app AQI badge in Korea, and the 미세먼지 alert that pings 50M phones on bad-haze days.
Six subcommands wrap two services:
| Command | Endpoint | Purpose |
|---|---|---|
| `scripts/realtime.sh` | `ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty` | 측정소별 실시간/일별/월별 측정값 (1h–90d). |
| `scripts/sido.sh` | `ArpltnInforInqireSvc/getCtprvnRltmMesureDnsty` | 시도별 실시간 측정값 (every station in a province). |
| `scripts/forecast.sh` | `ArpltnInforInqireSvc/getMinuDustFrcstDspth` | 1~3일 미세먼지/오존 예보. |
| `scripts/station.sh` | `MsrstnInfoInqireSvc/getMsrstnList` | 측정소 directory (filter by sido / addr keyword). |
| `scripts/tm.sh` | `MsrstnInfoInqireSvc/getTMStdrCrdnt` | 행정/법정동 → TM(중부원점) 좌표. |
| `scripts/nearby.sh` | `MsrstnInfoInqireSvc/getNearbyMsrstnList` | TM 좌표 → 가까운 측정소 목록 (정렬: 거리). |
All output is JSONL — one row per record — so it pipes directly into `jq`, `csvkit`, `pandas`, or downstream skills.
## When to use this skill
- **AQI dashboards** — `sido --sido 전국 --num 600` to get a country-wide snapshot in one call; cache hourly.
- **Location-based mask/runner alerts** — `tm --umd <my-동>` → `nearby --tm-x … --tm-y …` → `realtime --station <closest>` → push notification when `pm25Grade ≥ 3`.
- **School outdoor-activity advisories** — schedule `forecast --date $(date +%F)` daily; surface `informGrade` and `informCause` to staff/parents.
- **Korean-news AQI grounding** — when an LLM answers "오늘 강남 미세먼지 어때?", cite real-time station data, not hallucinated numbers.
- **Backfill / research panels** — `realtime --period 3MONTH` to pull the last 90 days for any single station; combine with `kr-holiday-cli` to test holiday-traffic effects.
## Do **not** use this skill for
- **Sub-hourly or sensor-grade data** — AirKorea publishes at hourly granularity (with a ~1h delay). For minute-level, use IoT vendors (Awair, IQAir partnerships).
- **Outside-Korea AQI** — only 한국환경공단 stations. For other countries, use OpenAQ, AirNow, or WAQI.
- **Health-effect interpretations** — the API returns concentrations + grades, not personalized health advice. Pair with WHO/CDC guidance text on the consumer side.
- **High-throughput public-facing apps without a prod-tier key** — dev-tier ceiling is 1,000 req/day per service. Apply for production tier on data.go.kr.
## Prerequisites
1. **Register at <https://www.data.go.kr/>** (Korean OpenData portal, free, no business required).
2. Apply for **both** services (auto-approved for the dev tier):
- "한국환경공단_에어코리아_대기오염정보" (real-time + forecast)
- "한국환경공단_에어코리아_측정소정보" (station directory + TM utilities)
3. Copy the **Decoding** key (the raw form, *not* URL-encoded) and export:
```bash
export AIRKOREA_SERVICE_KEY='your_decoded_key_here'
```
4. Optional overrides (rarely needed):
```bash
export ARPLTN_BASE='https://apis.data.go.kr/B552584/ArpltnInforInqireSvc'
export MSRSTN_BASE='https://apis.data.go.kr/B552584/MsrstnInfoInqireSvc'
```
## Common workflows
### A) Snapshot every Seoul station right now
```bash
scripts/sido.sh --sido 서울 --num 60 \
| jq -c '{stationName, dataTime, pm10Value, pm25Value, pm10Grade, pm25Grade}'
```
### B) Find my closest station from a 동 name (full chain)
```bash
read TMX TMY < <(scripts/tm.sh --umd 역삼동 \
| jq -r 'select(.sidoName=="서울") | "\(.tmX) \(.tmY)"' | head -1)
scripts/nearby.sh --tm-x "$TMX" --tm-y "$TMY" | head -3
# → e.g. "강남구", distance ~1.2km
scripts/realtime.sh --station 강남구 --period HOUR --num 1 \
| jq -c '{dataTime, pm25Value, pm25Grade, pm10Value, pm10Grade, o3Value, no2Value}'
```
### C) Daily forecast headline for a digest email
```bash
scripts/forecast.sh --date "$(date +%F)" --code PM25 \
| jq -r '"\(.informData) (\(.informCode)) — \(.informGrade // "-")"'
```
### D) 90-day station history → CSV
```bash
scripts/realtime.sh --station 종로구 --period 3MONTH --num 1000 \
| jq -r '[.dataTime, .pm10Value, .pm25Value, .o3Value, .no2Value] | @csv' \
> jongno_90d.csv
```
### E) Run a "very-bad" alert across the country
```bash
scripts/sido.sh --sido 전국 --num 1000 \
| jq -c 'select((.pm25Grade // "-") == "4")'
# emit one row per station that's in 매우나쁨 right now → fan out to your alerter
```
## Output shape — sample (realtime.sh)
```json
{
"dataTime": "2026-04-29 09:00",
"stationName": "강남구",
"mangName": "도시대기",
"so2Value": "0.003",
"coValue": "0.4",
"o3Value": "0.041",
"no2Value": "0.018",
"pm10Value": "47",
"pm25Value": "22",
"khaiValue": "78",
"khaiGrade": "2",
"so2Grade": "1",
"coGrade": "1",
"o3Grade": "1",
"no2Grade": "1",
"pm10Grade": "1",
"pm25Grade": "2"
}
```
Grade encoding (used by every `*Grade` field):
| Grade | 등급 | PM2.5 µg/m³ | PM10 µg/m³ |
|---|---|---|---|
| 1 | 좋음 | 0–15 | 0–30 |
| 2 | 보통 | 16–35 | 31–80 |
| 3 | 나쁨 | 36–75 | 81–150 |
| 4 | 매우나쁨 | 76+ | 151+ |
## Errors
| AirKorea resultCode | Meaning | This CLI |
|---|---|---|
| `00` | OK | success, JSONL emitted |
| `99` / XML body | 등록되지 않은 키 / Decoding key 미사용 | exit 22 |
| `01` | Application error | exit 22 |
| `04` | HTTP error | exit 22 |
| `30` | 서비스 키 권한 없음 | exit 22 |
| `12` | DEPRECATED OpenAPI | exit 22 |
| `33` | 일일 트래픽 초과 | exit 22 |
If you get an XML payload with `<errMsg>SERVICE_KEY_IS_NOT_REGISTERED_ERROR</errMsg>`, you most likely pasted the Encoded key — switch to the **Decoding** version on data.go.kr.
## Composability with other skills in this hub
| Pair with | Why |
|---|---|
| `juso-address-cli` | Resolve a Korean street address → 행정동 → feed into `tm.sh`. |
| `kakao-local-cli` | Convert WGS84 lat/lng (from a map tap) to TM via Kakao geocoding, then `nearby.sh`. |
| `kr-holiday-cli` | Overlay holiday weekends on AQI charts (commute drops on holidays — visible in NO2). |
| `naver-papago-translate` | Localize forecast strings (`informCause`) for non-KR-speaking users. |
| `tistory-api-cli` / `velog-cli` | Publish a daily AQI digest as a blog post. |
| `kosis-cli` | Cross-reference long-run AQI trends with population / industrial-emission stats. |
## License
MIT — see [LICENSE](./LICENSE). AirKorea data is © 한국환경공단; redistribution is permitted under the data.go.kr open-data terms (https://www.data.go.kr/ugs/selectPortalPolicyView.do).
don't have the plugin yet? install it then click "run inline in claude" again.
restructured into implexa's six-component format, added explicit decision-point branches for all known error codes and edge cases (rate limits, invalid key encoding, empty result sets, archive cutoff, TM coordinate validation), expanded inputs section with env vars and external integration pairs, documented output schemas for all six subcommands with jsonl format, clarified outcome signals for dashboards/alerts/grounding, and preserved original author's intent and procedure faithfully.
wrap Korea's first-party air-quality system (한국환경공단 에어코리아 OpenAPI, operated by the Korea Environment Corporation) to fetch real-time PM10, PM2.5, O3, NO2, CO, SO2 concentrations plus 1, 3 day forecasts, station metadata, and TM-coordinate lookups. use this skill to build AQI dashboards, mask/runner alerts, school outdoor-activity advisories, location-based station queries by 행정동 / 시도 / TM coordinates, or ground LLM answers about Korean air quality in regulator-curated data. data is hourly-granularity, 1, 3 day lag, and 1,000 req/day on the free dev tier.
required environment variable:
AIRKOREA_SERVICE_KEY: raw (non-URL-encoded) decoding key from data.go.kr registration. register at https://www.data.go.kr/, apply for both services (auto-approved dev tier): "한국환경공단_에어코리아_대기오염정보" (real-time + forecast) and "한국환경공단_에어코리아_측정소정보" (station directory + TM utilities). copy the Decoding key (not Encoded) from your registered app dashboard.optional environment variables:
ARPLTN_BASE: override default base URL https://apis.data.go.kr/B552584/ArpltnInforInqireSvc for real-time/forecast endpoints.MSRSTN_BASE: override default base URL https://apis.data.go.kr/B552584/MsrstnInfoInqireSvc for station info/TM endpoints.external integrations (optional pairs):
juso-address-cli: resolve Korean street addresses to 행정동 names before feeding into tm.sh.kakao-local-cli: convert WGS84 lat/lng to TM coordinates, then pass to nearby.sh.kr-holiday-cli: overlay holidays on AQI time-series (NO2 drops on holidays due to reduced commute).naver-papago-translate: localize forecast text (informCause) for non-Korean-speaking end-users.tistory-api-cli or velog-cli: publish daily AQI digests as blog posts.rate limits and quotas:
register and retrieve key
AIRKOREA_SERVICE_KEY ready for export.set environment and validate connectivity
AIRKOREA_SERVICE_KEY, ARPLTN_BASE and MSRSTN_BASE (optional).export AIRKOREA_SERVICE_KEY='<your_decoded_key>'.query real-time measurements by station
scripts/realtime.sh --station <name> --period <HOUR|DAY|MONTH|3MONTH> --num <limit>.query real-time measurements by province (시도)
scripts/sido.sh --sido <name> --num <limit>.query 1, 3 day air-quality forecasts
scripts/forecast.sh --date <YYYY-MM-DD> [--code <PM25|O3>].fetch station directory and filter
scripts/station.sh [--sido <name>] [--keyword <search>].convert 행정동 (administrative dong) to TM coordinates
scripts/tm.sh --umd <dong_name>.find nearby stations from TM coordinates
scripts/nearby.sh --tm-x <x> --tm-y <y>.pipe output to jq, csvkit, or downstream skills
jq -c 'select(.pm25Grade == "4")' to find bad-air stations.jq -r '[.dataTime, .pm10Value, .pm25Value] | @csv'.naver-papago-translate to localize forecast text for non-KR users.if AIRKOREA_SERVICE_KEY is missing or invalid:
if request returns resultCode 33 (daily traffic limit exceeded):
if request returns resultCode 30 (no service permission):
if resultCode 99, 01, 04, or 12 (malformed key, app error, HTTP error, deprecated API):
if a station query returns empty result set:
scripts/station.sh to list all valid station names in your region.if --period 3MONTH returns fewer than expected rows:
if TM coordinates are out of valid range (e.g., outside Korean territory):
nearby.sh returns empty or error (resultCode 01).tm.sh to regenerate coordinates.if you need data before 1h ago:
all commands emit JSONL (one JSON object per line, no array wrapping). piping works directly with jq, csvkit, pandas, or downstream Implexa skills.
realtime.sh schema:
{
"dataTime": "2026-04-29 09:00",
"stationName": "강남구",
"mangName": "도시대기",
"so2Value": "0.003",
"coValue": "0.4",
"o3Value": "0.041",
"no2Value": "0.018",
"pm10Value": "47",
"pm25Value": "22",
"khaiValue": "78",
"khaiGrade": "2",
"so2Grade": "1",
"coGrade": "1",
"o3Grade": "1",
"no2Grade": "1",
"pm10Grade": "1",
"pm25Grade": "2"
}
grade encoding (1, 4):
| Grade | Korean | PM2.5 µg/m³ | PM10 µg/m³ |
|---|---|---|---|
| 1 | 좋음 (good) | 0, 15 | 0, 30 |
| 2 | 보통 (fair) | 16, 35 | 31, 80 |
| 3 | 나쁨 (poor) | 36, 75 | 81, 150 |
| 4 | 매우나쁨 (very poor) | 76+ | 151+ |
sido.sh schema: same as realtime.sh, multiple stations per province.
forecast.sh schema:
{
"informDate": "2026-04-29",
"informCode": "PM25",
"informGrade": "2",
"informCause": "현재 대기 상태는 보통이며, 자동차 배기가스 등의 영향으로 내일 일부 지역에서 미세먼지가 높을 것으로 예상됩니다."
}
station.sh schema:
{
"stationName": "강남구",
"sidoName": "서울",
"districtName": "강남구",
"mangName": "도시대기",
"tmX": "328000.0",
"tmY": "553000.0",
"addr": "서울특별시 강남구 테헤란로 xxx"
}
tm.sh schema:
{
"sidoName": "서울",
"cityName": "강남구",
"districtName": "역삼동",
"tmX": "328100.5",
"tmY": "553050.3"
}
nearby.sh schema (sorted by distance, nearest first):
{
"stationName": "강남구",
"distance": "1.2",
"tmX": "328000.0",
"tmY": "553000.0",
"sidoName": "서울"
}
file locations: output goes to stdout; no files written unless piped (> file.jsonl).
jq and contains expected fields (no null errors, no XML error payloads).jq empty on output; if it succeeds, schema is valid.informGrade and informCause visible to staff/parents.scripts/sido.sh --sido 전국 --num 1000 | jq -c 'select(.pm25Grade == "4")' and confirm one row per "매우나쁨" station; fire notification to runners/schools if count > 0.