# Air Quality API - US AQI, Pollutants & Cleanest Hours Forecast (`rolescout/airscout-air-quality-aqi`) Actor

Air quality for any coordinates worldwide: current US AQI, PM2.5, PM10, ozone, NO2, SO2, CO and UV index, an EPA AirNow-based outdoor exercise verdict, and the cleanest and dirtiest windows up to 7 days ahead. Modelled Copernicus CAMS data via Open-Meteo. JSON/CSV rows, pay per query.

- **URL**: https://apify.com/rolescout/airscout-air-quality-aqi.md
- **Developed by:** [Dave Fergins](https://apify.com/rolescout) (community)
- **Categories:** Sports, Automation, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 88.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$20.00 / 1,000 query answereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

Air quality data for any coordinates worldwide as plain JSON or CSV rows: the current US AQI with the pollutant breakdown (PM2.5, PM10, ozone, nitrogen dioxide, sulphur dioxide, carbon monoxide and UV index), the EPA category and activity guidance, a GO / CAUTION / AVOID verdict on exercising outdoors right now, and the cleanest and dirtiest contiguous windows of the length you choose within the next 7 days. The data is modelled (Copernicus CAMS via Open-Meteo), not a reading from a sensor at your exact location, and the guidance is general public-health information, not medical advice.

The Actor is a thin client: it sends each query you give it to the air quality service, charges one event per query the service answers, and writes the answer to the dataset as one summary row, plus one row per forecast hour when you ask for the hourly series. A run takes seconds and uses 256 MB. You need no API key or account with any data provider; the Actor carries its own credential.

### Air quality now: US AQI, pollutants and an outdoor exercise verdict

One query is a point (`lat`, `lon`) anywhere in the world. The summary row carries the current reading: `time` (local, with the location's `timezone`), `us_aqi`, `pm2_5`, `pm10`, `ozone`, `nitrogen_dioxide`, `sulphur_dioxide`, `carbon_monoxide` (all in micrograms per cubic metre) and `uv_index`; the EPA `band` of the current AQI (Good, Moderate, Unhealthy for Sensitive Groups, and so on) with the matching `advice`; `exercise_verdict` (GO, CAUTION or AVOID); and `dominant_pollutant`, the one closest to its own limit now. A `summary` field holds the whole answer as one paragraph, with your `place` label in it if you gave one.

### The cleanest window ahead: when to go outside

`hours` says how far ahead to search (default 24, at most 168, which is 7 days) and `duration` how many hours you want to be outside (default 1). The service finds the cleanest contiguous block of that length and the dirtiest, and the summary row carries both flattened: `best_window_start`, `best_window_end`, `best_window_mean_us_aqi`, `best_window_peak_us_aqi`, `best_window_band`, and the same five for `worst_window`. `improving` is true when the best window is cleaner than now. Set `hourly` to true to add one `hourly` row per forecast hour with the same pollutant columns; it is most of the payload, so it is off by default.

### Several points in one run

The top-level fields are the first query and the defaults for every other. Add `points` as a list of "lat,lon" strings for more locations with the same settings, or `queries` as a list of objects using the same field names for anything else, for instance a different `duration` or `place` per point. A `queries` object with its own point does not inherit the top-level one; one that only changes a setting runs at the top-level point. The `place` label belongs to its point: extra points and queries carry their own label or none, and a `place` in a `queries` object needs its `lat` and `lon` beside it. A run may hold at most 25 queries, and each query the service answers is one billable event.

### Example input

```json
{
    "lat": 47.6588,
    "lon": -117.426,
    "hours": 24,
    "duration": 2,
    "hourly": false,
    "place": "Spokane",
    "points": ["45.5152,-122.6784"],
    "queries": [{ "lat": 34.0522, "lon": -118.2437, "place": "Los Angeles", "duration": 3 }]
}
```

That run is three queries: Spokane for a two-hour session within the next 24 hours, Portland with the same settings, and Los Angeles for a three-hour session. Field names are the service's own, so a `queries` object is exactly the query it sends.

### Output: example rows, CSV views and the OUTPUT record

Every row starts with the same eight columns: `scout`, `row_type` (summary or hourly), `query_index` (1-based position in the run), `query` (the normalised query as sent), `as_of` (the current reading's local time), `receipt`, `source` and `disclaimer`. Then the row's own fields. A summary row looks like this:

```json
{
    "scout": "air",
    "row_type": "summary",
    "query_index": 1,
    "query": { "duration": 2, "hours": 24, "lat": 47.6588, "lon": -117.426, "place": "Spokane" },
    "as_of": "2026-10-08T13:00:00-06:00",
    "receipt": "",
    "source": "Open-Meteo Air Quality API (CC-BY-4.0), based on Copernicus Atmosphere Monitoring Service (CAMS) models",
    "disclaimer": "Air quality is modelled (Copernicus CAMS via Open-Meteo) ...",
    "time": "2026-10-08T13:00:00-06:00",
    "timezone": "America/Los_Angeles",
    "us_aqi": 42,
    "band": "Good",
    "advice": "Air quality is good. No restrictions on outdoor activity.",
    "exercise_verdict": "GO",
    "dominant_pollutant": "ozone",
    "pm2_5": 9.8,
    "pm10": 14.1,
    "ozone": 61,
    "nitrogen_dioxide": 7.2,
    "sulphur_dioxide": 1.1,
    "carbon_monoxide": 182,
    "uv_index": 3.4,
    "improving": true,
    "best_window_start": "2026-10-08T20:00:00-06:00",
    "best_window_end": "2026-10-08T22:00:00-06:00",
    "best_window_mean_us_aqi": 28,
    "best_window_peak_us_aqi": 31,
    "best_window_band": "Good",
    "worst_window_start": "2026-10-08T15:00:00-06:00",
    "worst_window_end": "2026-10-08T17:00:00-06:00",
    "worst_window_mean_us_aqi": 55,
    "worst_window_peak_us_aqi": 58,
    "worst_window_band": "Moderate",
    "summary": "Spokane - US AQI 42 (Good). GO: Air quality is good. ..."
}
```

The dataset has two table views: `summary` (one line per query) and `hourly` (one line per forecast hour). Filter on `row_type` when you export both as one CSV. The run's `OUTPUT` key-value record holds the service's response for every query verbatim, keyed q1, q2, ..., each with the query it answered, plus an `errors` map for any query that failed.

### Pricing: pay per query answered

One `query-answered` event per query the service answered, whether or not you asked for the hourly series: one point is one event, ten points is ten events. Rows are never billed individually, so `hourly` changes the size of the dataset, not the price. A query the service refuses (coordinates it cannot model, the service warming up) is not charged, and the run still succeeds with the queries that worked; only a run where every query failed ends as failed. Price: $0.02 per query answered, on every Apify plan. Example: a daily schedule of three points is about 90 queries a month, $1.80. An hourly schedule for one point is about 720 queries a month, $14.40.

### Use cases: air quality monitoring, training plans and dashboards

- Air quality monitoring: schedule the run for the points you care about and keep the summary rows as a time series, with `as_of` on every row.
- Training and event planning: the cleanest `duration`-hour window in the next day or week, per location, ready for a calendar or a notification.
- Health and school notices: the EPA band, advice and GO / CAUTION / AVOID verdict per site, in a table.
- Spreadsheets and dashboards: export the `summary` view as CSV or attach Apify's Google Sheets integration so every scheduled run appends its rows.
- AI agents and automations: like any public Actor it can be called through the Apify API, a webhook or Apify's MCP server, with the same input and the same dataset.

### Data source and attribution

The data is the Open-Meteo Air Quality API (CC-BY-4.0), based on Copernicus Atmosphere Monitoring Service (CAMS) models: modelled values for any coordinates worldwide, up to 7 days ahead. The AQI is the US EPA scale and the activity guidance follows the US EPA AirNow categories. Every row carries the attribution in `source` and the service's disclaimer verbatim:

> Air quality is modelled (Copernicus CAMS via Open-Meteo), not a reading from a sensor at your exact location; local conditions near fires, traffic or industry can differ sharply. Activity guidance follows US EPA AirNow categories and is general public-health information, not medical advice — if you have a heart or lung condition, follow your clinician's advice.

### FAQ

**Does it work outside the US?** Yes, anywhere in the world: the model is global. The AQI scale and the activity categories are the US EPA's, applied everywhere.

**Is this a sensor reading?** No. Values are modelled by Copernicus CAMS, served through Open-Meteo, for the grid cell around your point. Conditions near fires, traffic or industry can differ sharply from the model.

**What is the cleanest window?** The contiguous block of `duration` hours, within the next `hours` hours, with the lowest mean US AQI; `worst_window` is the opposite. Both carry the mean, the peak and the EPA band.

**Can I get the full hourly forecast?** Yes: set `hourly` to true and each forecast hour becomes a row with `us_aqi`, `pm2_5`, `ozone` and the other pollutants where modelled. The price does not change.

**Can I check several locations in one run?** Yes: `points` for more "lat,lon" locations with the same settings, or `queries` for a list of full query objects. At most 25 queries per run, one event each.

**What are the pollutant units?** Micrograms per cubic metre for PM2.5, PM10, ozone, nitrogen dioxide, sulphur dioxide and carbon monoxide; `uv_index` is the UV index.

**Do I need an API key?** No. The Actor carries its own credential for the air quality service; you only give it the queries.

**Why did one query fail while the others worked?** The service answers each query on its own; a query it cannot answer is recorded in the status message and the `OUTPUT` record's `errors` map, skipped without charge, and the rest continue. A query the service never answers (unreachable, or a gateway error) is retried once; after two such queries in a row the remaining ones are skipped rather than waited for.

**What happens with an empty input?** The run succeeds with one unbilled `status` row (`status: needs_input`) that names the missing fields, gives a hint and repeats the example input. Nothing is sent to the service and nothing is charged. Any field that is present must still be valid.

# Actor input Schema

## `lat` (type: `number`):

Decimal degrees. Required.

## `lon` (type: `number`):

Decimal degrees. Required.

## `hours` (type: `integer`):

How far ahead, in hours, to look for the cleanest window: 1 to 168 (7 days).

## `duration` (type: `integer`):

How many hours you want to be outside: the cleanest window is a contiguous block this long, 1 to 168 and no longer than the hours searched.

## `hourly` (type: `boolean`):

Adds one row per forecast hour (US AQI, PM2.5, ozone and the other pollutants where modelled). It is most of the payload; off by default.

## `place` (type: `string`):

Optional label used in the summary text, e.g. Spokane. It names this point only: extra points and queries carry their own label or none.

## `points` (type: `array`):

Extra "lat,lon" points, one query each, with the fields above as defaults. Each is one billable event; at most 25 queries per run.

## `queries` (type: `array`):

A list of query objects using the same field names as above, each merged over the top-level fields. At most 25 queries per run in total.

## Actor input object example

```json
{
  "lat": 47.6588,
  "lon": -117.426,
  "hours": 24,
  "duration": 2,
  "hourly": false,
  "place": "Spokane",
  "points": [
    "45.5152,-122.6784"
  ],
  "queries": [
    {
      "lat": 34.0522,
      "lon": -118.2437,
      "place": "Los Angeles",
      "duration": 3
    }
  ]
}
```

# Actor output Schema

## `rows` (type: `string`):

Summary and hourly rows for every query, with the query object on each.

## `summary` (type: `string`):

One line per query: now, verdict, windows. Filter on row_type = summary.

## `hourly` (type: `string`):

One line per forecast hour when hourly is on. Filter on row_type = hourly.

## `answers` (type: `string`):

The service's response for each query verbatim, keyed q1, q2, ..., plus an errors map for any query that failed.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "lat": 47.6588,
    "lon": -117.426,
    "hours": 24,
    "duration": 2
};

// Run the Actor and wait for it to finish
const run = await client.actor("rolescout/airscout-air-quality-aqi").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "lat": 47.6588,
    "lon": -117.426,
    "hours": 24,
    "duration": 2,
}

# Run the Actor and wait for it to finish
run = client.actor("rolescout/airscout-air-quality-aqi").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "lat": 47.6588,
  "lon": -117.426,
  "hours": 24,
  "duration": 2
}' |
apify call rolescout/airscout-air-quality-aqi --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rolescout/airscout-air-quality-aqi"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/LDDqEXh7jSwsRDRAD/builds/2lRGWRobEuefNarX6/openapi.json
